一起看很美的日落!
一起看很美的日落!
题目描述
牛可乐有一棵由 个结点构成的树,第 个节点的权值为 。
我们定义一个连通块 的权值为:
- 当前连通块中两两结点的权值异或和,即 ;
你需要计算全部连通块的权值之和。由于答案可能很大,请将答案对 取模后输出。
此题中的连通块定义为:对于树上的任意一个点集 ,如果 中的任意两点 之间存在一条路径,且路径上的所有点都在 中,则称 是一个连通块。
输入描述:
第一行输入一个整数 代表树上的节点数量。
第二行输入 个整数 代表每个节点的权值。
此后 行,第 行输入两个整数 代表树上第 条边连接节点 和 。
输出描述:
输出一个整数,代表全部连通块的权值和。答案可能很大,请将答案对 取模后输出。
示例1
输入
3
5 2 1
1 2
1 3
输出
50
说明
在这个样例中,一共有 个连通块,每一个连通块的权值依次为:
- ,记为 ,权值为 ;
- ,记为 ,权值为 ;
- ,记为 ,权值为 ;
- ,记为 ,权值为 ;
- ,记为 ,权值为 ;
- ,记为 ,权值为 ;
示例2
输入
4
5 6 3 1
1 2
1 3
2 4
输出
142
解题思路
大概想了下发现很复杂要维护很多东西,以为错了就直接看题解,没想到还真这么做。
假设以节点 作为整颗树的根。题目中的连通块其实就是以任意节点为根的子树,因此我们可以按子树的根把所有的连通块(子树)分成 类,然后分别求每一类连通块的结果最后累加即可。由于涉及到位运算,我们可以对节点的权值进行拆位分别处理,这样每个节点的权值只有 和 两种情况,最后将算出的结果乘以相应的 的次幂,就是该位对答案的贡献。
现在有一个集合 ,元素只有 和 ,数量分别记为 和 。根据异或的性质,该集合中任意两个元素的异或值的和就是 ,记为 。现在有集合 要与 进行合并, 和 的数量就变成了 和 ,异或值的和就是
假设有 个集合 要与 个集合 进行合并,那么所有合并结果的异或值的和就是
同理合并后的集合数量就是 ,所有集合 的数量就是 ,所有集合 的数量就是 。
假设我们现在处理的是第 位,用 表示二进制下节点 权值第 位的值。假设 有 个子节点,记为 。那么以 为根的子树就可以通过以 为根的所有子树进行任意组合得到,可以用 dp 求出以 为根的所有子树的任意两个节点异或值的和。
定义 表示以 为根的子树组合得到的所有以 为根的子树的任意两个节点异或值的和, 表示以 为根的子树组合得到的所有以 为根的子树的数量, 和 分别表示以 为根的子树组合得到的所有以 为根的子树的权值为 和 的节点数量。根据以 为根的子树进行状态划分,可以把以 为根的子树看成上述若干个集合 ,把以 为根的子树组合得到的所有以 为根的子树看成若干个集合 , 与 两两进行合并。
初始只有一个节点 ,此时有 ,,,。参考上面的式子,状态转移方程就是
在代码实现中省略了所有状态的最后一维。
那么第 位对答案的贡献就是 。最后要求的答案就是 ,多乘一个 是因为上述考虑的是两个节点的组合,而题目要求考虑两个节点的排列。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5, M = N * 2, mod = 1e9 + 7;
int a[N];
int h[N], e[M], ne[M], idx;
int f[N], g[N], c[N][2];
void add(int u, int v) {
e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}
void dfs(int u, int p, int k) {
f[u] = 0, g[u] = 1, c[u][a[u] >> k & 1] = 1, c[u][~a[u] >> k & 1] = 0;
for (int i = h[u]; i != -1; i = ne[i]) {
int v = e[i];
if (v == p) continue;
dfs(v, u, k);
f[u] = (f[u] + 1ll * f[u] * g[v] + 1ll * f[v] * g[u] + 1ll * c[u][0] * c[v][1] + 1ll * c[u][1] * c[v][0]) % mod;
c[u][0] = (c[u][0] + 1ll * c[u][0] * g[v] + 1ll * c[v][0] * g[u]) % mod;
c[u][1] = (c[u][1] + 1ll * c[u][1] * g[v] + 1ll * c[v][1] * g[u]) % mod;
g[u] = (g[u] + 1ll * g[u] * g[v]) % mod;
}
}
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
memset(h, -1, sizeof(h));
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
int ret = 0;
for (int i = 0; i < 30; i++) {
dfs(1, 0, i);
for (int j = 1; j <= n; j++) {
ret = (ret + f[j] * (1ll << i)) % mod;
}
}
cout << ret * 2 % mod;
return 0;
}
参考资料
2025牛客寒假算法基础集训营2 出题人题解:https://blog.nowcoder.net/n/906fd00ff386438b9d63013a3760e73a
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/18690873
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2023-01-25 Rank Transform of a Matrix