传送门
题意
给定一颗大小为\(n\)带点权的树, 和一个整数\(k\)
询问是否可以删除至多\(k-1\)条边(至少1条), 将树分成至多\(k\)个连通块,使得每个连通块中的点权异或和相同
题解
这种题的思路大概就是考虑分成很多连通块时, 是否能用较少的联通块等效代替
容易想到, 如果划分成\(m\)个异或和相同的联通块,当\(m\)为偶数,肯定也能分成两个异或和为0的连通块,
同理, \(m\)为奇数, 也能分成3个相同的连通块
也就是,要么两个, 要么三个, 分类讨论嘛:
-
两个的话简单, 肯定将原树和他的一颗子树分开
枚举子树即可 -
三个的话, 也就是说, 我们要将原树的两颗子树分离开
那么继续讨论, 两颗子树是否相交:
如果不相交的话, 我们假设三颗子树的异或和分别为: \(a, b, c\)
显然:要满足 \(a \oplus (b \oplus c) = b = c\)
因为\(b = c\)得到 \(b \oplus c = 0\)
所以\(a = b = c\)
统计是否存在即可
如果相交的话, 显然: \(a \oplus b = b \oplus c = c\)
得到\(a = c , b = 0\)
依然统计即可
因为a一定为根节点, 上面两个不难统计
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int read(){
int num=0, flag=1; char c=getchar();
while(!isdigit(c) && c!='-') c=getchar();
if(c == '-') c=getchar(), flag=-1;
while(isdigit(c)) num=num*10+c-'0', c=getchar();
return num*flag;
}
const int N = 290005;
int T, n;
int A[N], sum[N], a[N], cnt[N], fa[N];
vector<int> p[N];
int flag;
int rflag = 0;
void dp(int x){
sum[x] = a[x];
for(auto nex : p[x]) {
if(nex == fa[x]) continue;
fa[nex] = x;
dp(nex);
sum[x] ^= sum[nex];
}
}
void dfs(int x){
int res = 0;
for(auto nex : p[x]){
if(nex == fa[x]) continue;
dfs(nex);
if(cnt[nex]) res++;
if(cnt[nex] && sum[x]==0) rflag=1;
}
if(res >= 2) rflag = 1;
if(sum[x] == sum[1] || res) cnt[x]=1;
}
int main(){
T = read();
while(T--){
n = read(); int k=read();
for(int i=1; i<=n; i++) {
sum[i] = 0;
cnt[i] = 0;
p[i].clear();
a[i] = read();
}
for(int i=1; i<n; i++){
int u=read(), v=read();
p[u].push_back(v);
p[v].push_back(u);
}
flag = 0; rflag=0;
dp(1);
for(int i=2; i<=n; i++){
if((sum[1]^sum[i]) == sum[i]) flag=1;
}
if(!flag && k>=3){
dfs(1);
flag = rflag;
}
printf(flag?"YES\n":"NO\n");
}
return 0;
}