[AGC052B] Tree Edges XOR 题解

发布时间 2023-07-12 15:25:33作者: 霜木_Atomic

[AGC052B] Tree Edges XOR

蛮好的题目。

题意

给你一棵树,和每条边的初始权值与目标权值,每次操作可以将一条边相连的两点所连接的其他边全部异或上这条边的边权,请问最后能否使每条边变成目标权值。

思路

首先考虑性质。我们发现每次操作都涉及很多边的变化,很麻烦,考虑去找不变的量。我们发现,如果考虑一条路径上的权值异或和,每次对一条边操作后,只会改变以这条边所连的两个点之一为一个起点,并且不包含这条边的路径的权值异或和。我们可以考虑以任一点为根,把一个点到根的路径上的权值和设为点权,这样每次操作,相当于是去交换这条边所连接的两点的点权。但是这样做有个问题,根节点如果交换会乱套,我们只需要把根节点连接上一个虚点,把连接的边权变成 \(0\) 即可,最后就是判断初始这颗树上的点权能否通过互换变成最终的树。显然,只要点权一一对应,那这两棵树必定能通过变换得到。
但是这样做还没完,因为有一个地方忽略了——根节点(不是虚点!)。我们仅仅只能指定初始树的根节点点权为 \(0\),但是互换之后,根节点的点权可不一定是 \(0\),而点权的含义就是这个点到根节点路径上的边权异或和,也就是说,最后的虚点到根上的边会有边权,那我们求出的每一个目标点权(设为 \(w2\))都应该异或上这个边权(设为 \(e\))。这时候,题目给了另一个性质—— \(n\) 是奇数,也就是说,除去根节点,最终会有偶数个点会异或上这个 \(e\)。那么,原始点权与我们求出的目标点权的异或和,会把偶数个 \(e\) 消掉,并把相同的边权消掉,最后剩下的就是 \(e\)
最后,我们把求出的目标点权都异或上这个 \(e\),就可以得到真正的目标点权。
代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;

inline int read(){
	int x = 0; char ch = getchar();
	while(ch<'0' || ch>'9') ch = getchar();
	while(ch>='0'&&ch<='9') x = x*10+ch-48, ch = getchar();
	return x;
}
struct node{
	int nxt, to, wa, wb;
}edge[N<<1];
int head[N], tot;
void add(int u, int v, int wa, int wb){
	edge[++tot].nxt = head[u];
	edge[tot].to = v;
	edge[tot].wa = wa;
	edge[tot].wb = wb;
	head[u] = tot;
}

int w1[N], w2[N];
int rootval;
void dfs(int u, int fath){
	for(int i = head[u]; i; i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fath) continue;
		w1[v] = w1[u]^edge[i].wa;
		w2[v] = w2[u]^edge[i].wb;
		dfs(v, u);
	}
}
int n;

int main(){
	n = read();
	for(int i = 1; i<n; ++i){
		int u = read(), v = read(), wa = read(), wb = read();
		add(u, v, wa, wb);
		add(v, u, wa, wb);
	}
	dfs(1, 0);
	for(int i = 1; i<=n; ++i){
		rootval^=(w1[i]^w2[i]);
	}
	for(int i = 1; i<=n; ++i){
		w2[i]^=rootval;
	}
	int tmp = 0;
	sort(w1+1, w1+1+n);
	sort(w2+1, w2+1+n);
	for(int i = 1; i<=n; ++i){
		if(w1[i] != w2[i]) {
			puts("NO");
			return 0;
		}
	}	
	puts("YES"); 
	return 0;
}