P9745 「KDOI-06-S」树上异或 (位运算trick+树形 dp)
2024.11.15
位运算trick+树形 dp
求出每个方案的答案肯定不现实。考虑转化贡献,我们求连通块对每个方案的贡献就行。计数问题当然首想 dp。
位运算又可以考虑按位计算。这样每个连通块就只有
两种贡献了,直接可以放到状态里。当然每个方案也就只有 。 维护什么答案?如果我们知道当前所有方案的权值之和,那么新添加的连通块就可以看做一个乘数直接乘在答案上,这是很好维护的。
注意到以
为根的连通块在还没枚举完子树前,无法确定贡献,所以就先不考虑它。 所以自然就设
表示 子树内部的所有断边方案的权值之和(除 所在连通块), 所在连通块的贡献为 。那么对应的最终要求的就是 表示 子树内部的所有断边方案的权值之和。 转移之一:
如果考虑的是第
位,以 为根的连通块对 的贡献就是 。 按位考虑完全可以放在 dfs 中,所以
的状态再加一位描述哪一位就行。 答案是
。
看到题目中贡献的计算,可以想到乘法分配律,也就是一个连通块的乘积可以直接乘在当前所有方案的权值之和上。
可以考虑特殊性质:链。那么树的问题就变成了序列问题。容易设
复杂度是
可以设
实际上在
考虑树上的做法,其实完全类似,就是把式子搬到树上,这里就不列举,重点还是序列问题时的思路。
具体在写转移的时候要注意把当前状态存起来。
复杂度
#include <bits/stdc++.h>
#define pii std::pair<int, int>
#define fi first
#define se second
#define pb push_back
typedef long long i64;
const i64 iinf = 0x3f3f3f3f, linf = 0x3f3f3f3f3f3f3f3f;
const int N = 5e5 + 10, mod = 998244353;
int n;
i64 a[N];
i64 f[N];
int g[N][65][2];
std::vector<int> e[N];
void dfs(int u, int fa) {
for(int i = 0; i <= 63; i++) g[u][i][(a[u] >> i) & 1] = 1;
for(auto v : e[u]) {
if(v == fa) continue;
dfs(v, u);
for(int i = 0; i <= 63; i++) {
i64 u0 = g[u][i][0], u1 = g[u][i][1]; //先存起来,防止转移错误
g[u][i][0] = (u0 * (g[v][i][0] + f[v]) % mod + u1 * g[v][i][1] % mod) % mod;
g[u][i][1] = (u0 * g[v][i][1] % mod + u1 * (f[v] + g[v][i][0]) % mod) % mod;
}
}
for(int i = 0; i <= 63; i++) f[u] = (f[u] + (1ll << i) % mod * g[u][i][1]) % mod;
}
void Solve() {
std::cin >> n;
for(int i = 1; i <= n; i++) std::cin >> a[i];
for(int i = 2; i <= n; i++) {
int u;
std::cin >> u;
e[u].pb(i), e[i].pb(u);
}
dfs(1, 0);
std::cout << f[1] << "\n";
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
Solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具