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;
}

浙公网安备 33010602011771号