12 收心赛2 T2 异或树 题解

异或树

题面

给定一棵有根树 \(T\) ,其初始只包含一个点权为 \(k_0\) 的根节点,接下来有 \(q\) 次操作如下:

  • 形如 1 x ,代表对于所有当前 \(T\) 的叶子扩张一次:设其权值为 \(w\) ,将一个权值为 \(w\) 和一个权值为 \(w \oplus x\) 的节点加入并作为其儿子
  • 形如 2 x ,代表询问 \(T\) 有多少棵子树满足子树内的所有节点权值的异或和为 \(x\) ,答案对 \(998244353\) 取模

其中 \(\oplus\) 表示按位异或

\(1 \le q \le 8000, \ 0\le x,k_0 \le 2^{13} - 1)\)

题解

这道题刚看的时候感觉格外麻烦,而且考场上把题读错了,部分分也没拿到

后来看了题解,理解了一下,发现跟考场上的思路有点像,不过当时只想着打完部分分跑路

回到这道题,仔细想想加点的过程,手模一下,发现处于高度 \(> 1\) 层的点是不会受后续加边的影响的,因为对于高度 \(>1\) 的层,设其最底层某个点的权值为 \(k\),考虑下面加点,每次异或和会怎么变,会加一对 \(k\) 以及一个 \(x\) ,因为层数 \(>1\) 所以最底层节点一定为偶数,所以这些新加的异或和都会抵消掉,不必考虑

所以我们只需要维护最底下一层,只有这一层会因为下面加点点权发生变化

所以设 \(f(x)\) 表示现在权值为 \(x\) 的子树有多少棵,\(g(x)\) 表示最低一层权值为 \(x\) 的点有多少个,那么每次加点的时候我们只需要将 \(g(x)\) 减掉,然后将其两个儿子节点的权值加上即可

code

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

typedef long long ll;

const int N = 8200;
const int mod = 998244353;

ll f[N], g[N], tmp[N];

int main () {
    freopen ("xortree.in", "r", stdin);
    freopen ("xortree.out", "w", stdout);
    int k0, m;
    cin >> k0 >> m;
    f[k0] = g[k0] = 1;
    for (int i = 1; i <= m; i++) {
        int op, x;
        cin >> op >> x;
        if (op == 1) {
            for (int j = 0; j <= 8191; j ++) {
                f[j ^ x] += (g[j] << 1);
                f[j ^ x] %= mod;
                tmp[j] = g[j];
            }
            for (int j = 0; j <= 8191; j ++) {
                g[j ^ x] += tmp[j];
                g[j ^ x] %= mod;
            }
        } else {
            f[x] = (f[x] % mod + mod) % mod;
            cout <<  f[x] << endl;
        }
    }
    return 0;
}
posted @ 2025-08-28 14:20  michaele  阅读(45)  评论(0)    收藏  举报