题解:CF1592C Bakry and Partitioning
本蒟蒻的第一篇题解IoI,求管理大大审过IoI
原题链接
好啦,这道题是什么意思呢?
就是给我们一棵树,每个节点有一个权值 \(a_i\),我们要化身园丁,我们被允许剪掉 \(1\)~\(k-1\) 条边,由于我们有强迫症,所以我们需要使得剪掉边后形成的所有连通块中,每个连通块内节点权值的异或和都相等。问是否存在这样的裁剪方案(即是否可以治好强迫症)。
思路
我们看到异或就能想的什么呢?
对了!就是 \(x \oplus x = 0\)
又由于剪完后的每个连通块内节点权值的异或和都必须相等,所以我们就能想到最后一定只有两种情况!
设整棵树的异或和 \(S = a_1 \oplus a_2 \oplus \dots \oplus a_n\)。
- 情况 1: \(S = 0\)
如果整棵树的异或和为 0,那么我们只需要删除任意一条边,得到两个连通块。因为总异或和为 0,所以这两个连通块的异或和一定相等。题目保证 \(k \ge 2\),所以至少可以删 1 条边。
因此,当 \(S = 0\) 时,直接输出 YES。
- 情况 2: \(S \ne 0\)
- 如果 \(k = 2\),我们只能删 1 条边(即分成 2 个连通块)。两个连通块异或和相等的话,它们的异或和必定都是 \(S\),但已知 \(S \ne 0\),矛盾。因此直接输出
NO。 - 如果 \(k \ge 3\),我们可以删 2 条边,把树分成 3 个连通块。要使 3 个连通块的异或和相等,且总异或和为 \(S\),那么这 3 个连通块的异或和必定都是 \(S\)(因为 \(S \oplus S \oplus S = S\))。
- 我们在树上进行自底向上的 DFS。对于节点 \(u\),计算它所在子树的异或和
xr[u]。 - 如果
xr[u] == S,说明我们找到了一个合法的连通块。我们将答案计数cnt++,并将xr[u]置为 0,相当于把它从树中切掉,不再影响父节点。 - 最后如果
cnt >= 3,说明可以分出来至少 3 个异或和为 \(S\) 的连通块,剩余的碎片异或和一定是 0(可以并入其他块),所以输出YES。否则输出NO。
- 我们在树上进行自底向上的 DFS。对于节点 \(u\),计算它所在子树的异或和
- 如果 \(k = 2\),我们只能删 1 条边(即分成 2 个连通块)。两个连通块异或和相等的话,它们的异或和必定都是 \(S\),但已知 \(S \ne 0\),矛盾。因此直接输出
复杂度
- 时间复杂度:\(O(n)\)。只需要一次
DFS即可遍历整棵树。 - 空间复杂度:\(O(n)\)。需要存储树的邻接表以及相关数组。
具体见代码。
\(\boxed{\text{Code}}\)
/*
writer: Luna_Frome_Starry
Problem: Bakry and Partitioning
Knowledge: DFS
Time: O(n)
*/
/*
Note:
*/
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
const int M = 100005;
const int INF = 1e9;
const int mod = 998244353;
int head[N], nxt[N * 2], to[N * 2], edge_cnt;
int a[N], xr[N]; //原数组&子树的异或和数组
int n, k, sum_xor, cnt;
void add_edge(int u, int v){ //链式前向星
to[++edge_cnt] = v;
nxt[edge_cnt] = head[u];
head[u] = edge_cnt;
}
void dfs(int u, int f){
xr[u] = a[u]; //u在自己的子树中
for(int i = head[u]; i; i = nxt[i]){
int v = to[i];
if(v == f){
continue;
}
dfs(v, u);
xr[u] ^= xr[v]; //计算xr[u]
}
if(xr[u] == sum_xor){ //记录cnt
cnt++;
xr[u] = 0;
}
}
void work(){
cin >> n >> k;
for(int i = 1; i <= n; i++){
head[i] = 0;
}
edge_cnt = 0;
sum_xor = 0;
for(int i = 1; i <= n; i++){
cin >> a[i];
sum_xor ^= a[i];
}
for(int i = 1; i < n; i++){
int u, v;
cin >> u >> v;
add_edge(u, v);
add_edge(v, u);
}
if(sum_xor == 0){ //判断
cout << "YES\n";
return ;
}
if(k == 2){
cout << "NO\n";
return ;
}
cnt = 0;
dfs(1, 0);
if(cnt >= 3){
cout << "YES\n";
}else{
cout << "NO\n";
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int oT_To = 1;
cin >> oT_To; //神秘多组
while(oT_To--){
work();
}
return 0; //不要忘了
}

https://img2024.cnblogs.com/blog/3862811/202610/3862811-20261021104518023-2132443286.png
浙公网安备 33010602011771号