QOJ7232 Odd Grammar

QOJ7232 Odd Grammar

你初始有两种物品,记作 \(\tt{a}\) 和 \(\tt{b}\)。现存在 \(n\) 种高级物品,给定 \(m\) 条合成规则,第 \(i\) 种合成规则形如使用 \([d_{i,1},d_{i,2},\cdots,d_{i,k_i}]\) 合成物品 \(c_i\),满足 \(c_i \in [1,n]\),\(d_{i,j}\) 可能是 \(\tt{a},\tt{b}\) 或 \(\in[1,n]\)。

判定是否存在某种合成方式,从奇数个初始物品开始,合成后仅剩余 \(1\) 个 \(1\) 号物品。

多测,\(\sum n \le 5 \times 10^4\),\(\sum m \le 2\times 10^5\),输入字符数 \(\le 5\text{MB}\),2 秒,512 MB。

将第 \(i\) 种合成规则作为虚点 \(i'\),连边 \(d_{i,j} \to i'\) 和 \(i' \to c_i\)。虚点 \(i'\) 激活当且仅当所有前驱 \(d_{i,j}\) 激活,实点 \(c_i\) 激活只要存在前驱 \(i'\) 激活。

将合并过程视作一棵 \(1\) 为根的根向生成树,题目要求叶子 \(\tt{a},\tt{b}\) 的数目为奇数。

设 \(S_u\) 为合成 \(u\) 可实现的叶子数奇偶性,记录 \(S_x \subseteq \{0,1\}\) 只有 \(4\) 种。初始 \(S_{\tt{a}}=S_{\tt{b}}=\{1\}\),判定 \(1 \in S_1\)。实点是逻辑 OR,对应集合并,虚点是逻辑 AND。

对虚点 \([d_1,d_2,\cdots,d_k] \to c\) 简单讨论:

  • 若 \(\exists i, S_{d_i}=\varnothing\),原料不可用,规则暂时失效。
  • 若 \(\forall i, \left|S_{d_i}\right|=1\),所有原料只给出一种奇偶性,最终产生 \(p_1 \oplus p_2\oplus \cdots p_k\)。
  • 若 \(\exists i, |S_{d_i}|=2\),调整 \(d_i\) 的取值可以两种奇偶。

使用 BFS 更新状态,当物品的 \(S_u\) 改变时扔进队列,并更新所有关联点。更新至多 \(2\) 次,故总复杂度线性。

实现时,只要维护连向虚点的 \(\varnothing\) 数目,是否有 \(\{0,1\}\) 连入,以及入点异或和等。视 \(n,m,\sum k_i\) 同阶复杂度 \(O(n)\)。

const int N = 2.5e5 + 5;
int n, m, cnt[N], rem[N], sum[N];
bool vis[2][N], has2[N];
vector<int> e[N];
queue<pair<int, int>> Q;

void add(int u, int op) {
    if (vis[op][u]) return;
    vis[op][u] = true, Q.emplace(u, op);
}

void _main() {
    cin >> n >> m;
    if (n == 0 && m == 0) exit(0);
    const int A = n+m+1, B = n+m+2;
    for (int i = 1; i <= B; i++) {
        e[i].clear();
        cnt[i] = rem[i] = sum[i] = 0;
        vis[0][i] = vis[1][i] = has2[i] = false;
    }
    auto read = [&]() -> int {
        string s; cin >> s;
        if (s == "a") return A;
        if (s == "b") return B;
        int x = 0;
        for (char ch : s) x = x * 10 + ch - '0';
        return x;
    };
    for (int i = 1, v, k; i <= m; i++) {
        cin >> v >> k; rem[i+n] = k;
        if (k == 0) add(v, 0);
        for (int u; k--; ) u = read(), e[u].pb(i+n);
        e[i+n].pb(v);
    }
    add(A, 1), add(B, 1);
    while (!Q.empty()) {
        auto [u, op] = Q.front(); Q.pop();
        if (u > n && u < A) {
            for (int v : e[u]) add(v, op);
        } else {
            cnt[u]++;
            for (int v : e[u]) {
                if (cnt[u] == 1) rem[v]--, sum[v] ^= op;
                else has2[v] = true;
                if (rem[v] != 0) continue;
                if (!has2[v]) add(v, sum[v]);
                else add(v, 0), add(v, 1);
            }
        }
    }
    cout << (vis[1][1] ? "YES\n" : "NO\n");
}
posted @ 2026-10-26 16:58  stripe_python  阅读(7)  评论(0)    收藏  举报