题意
给定一棵树,树上每个结点取值为
2
0
<
=
a
i
<
2
30
2^0<=a_i<2^{30}
20<=ai<230,如果树上存在路径path(u,v)使得,路径上的结点的异或和为0,则这条路径是损坏的。
现能够将树上任一结点的数,修改成其他的数值(可以超出
2
30
2^{30}
230)。
问,最少需要修改多少个结点,才能使树上不存在损坏的路径。
思路
- 对于损坏的路径,我们只需要把路径上的一个结点,修改成 2 31 2^{31} 231即可,因为树上结点的取值都小于 2 31 2^{31} 231,与之异或,必定非0。
- 对于损坏的路径,我们要修改离根节点最近的那个点,因为这个结点,影响面最大。修改该结点,可以最大保证破坏更多的损坏路径。
- 构建以1为根结点root的树
- 定义dis(u)为结点u与根结点组成的路径的异或和。
- 根据异或性质,对于任意结点u,v,假设它们的公共祖先为p,则路径(u,v)的异或值,实际等于 d i s ( u ) ⨁ d i s ( v ) ⨁ p dis(u)\bigoplus dis(v)\bigoplus p dis(u)⨁dis(v)⨁p
我们可以预处理,计算每个结点的dis值。dfs遍历每个结点(优先遍历子结点),对于存在损坏的路径,则破坏该路径的公共祖先结点。
在dfs遍历的时候,用set维护每个结点的所有后代结点的dis值。
注意事项
- 该题卡常,遍历数组时需要遍历小的那个,详见代码33行
- 需要清空子节点的 set集合,不然内存会爆,详见代码42行
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 200010;
int n, x, y, ans;
int a[maxn], dis[maxn];
vector<int> g[maxn];
set<int> st[maxn];
// dis[u] = a[u] ^ a[parent(u)] ^ ... ^ a[root]
void cal_pre(int u, int p) {
dis[u] = a[u];
if (p != -1) {
dis[u] ^= dis[p];
}
for (auto v: g[u]) {
if(v == p) {
continue;
}
cal_pre(v, u);
}
}
void dfs(int u, int p) {
st[u].insert(dis[u]);
bool same = false;
for (auto v: g[u]) {
if (v == p) {
continue;
}
dfs(v, u);// dfs the son node first.
// if no this, TLE happen.
if (st[v].size() > st[u].size()) {
st[u].swap(st[v]);
}
for (auto x: st[v]) {
same |= st[u].count(x ^ a[u]);
}
for (auto x: st[v]) {
st[u].insert(x);
}
st[v].clear();// if no this, Memory Limit happen.
}
if (same) {
st[u].clear();
++ans;
}
}
void solve() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
g[i].clear();
st[i].clear();
}
for (int i = 1; i < n; ++i) {
scanf("%d%d", &x, &y);
g[x].push_back(y);
g[y].push_back(x);
}
cal_pre(1, -1);
ans = 0;
dfs(1, -1);
printf("%d\n", ans);
}
int main() {
int t;
// scanf("%d", &t);
t = 1;
while (t--) {
solve();
}
}
最后
weixin gongzhonghao搜 对方正在debug,关注下,一起快乐刷题吧