2021-12-24
文章目录
D. X(or)-mas Tree
题目大意:
圣诞节前一天晚上,圣诞老人正在疯狂地布置他的新圣诞树!树中有 n
个节点,由 n-1
条边连接。在树的每条边上,都有一组圣诞灯,可以用二进制表示的整数表示。
他让精灵们过来欣赏他的树。每个小精灵都被分配了两个节点 a
和 b
,这个小精灵看着这两个节点之间的简单路径上的所有灯。在此之后,小精灵最喜欢的数字成为该路径边缘上的灯光值的按位异或。
然而,北极一直在从一场严重的流感中恢复过来。正因为如此,圣诞老人忘记了他在树上放的一些灯的配置,他已经离开了北极!幸运的是,小精灵们挺过来了,每个人都告诉圣诞老人他被分配了哪对节点
(
a
i
,
b
i
)
(a_i,b_i)
(ai,bi),以及他最喜欢的数字中设置位数的奇偶性。换句话说,当他最喜欢的数字用二进制写成时,他记得 1
的数量是奇数还是偶数。
帮助圣诞老人确定记忆是否可能是一致的,如果是,请记住他的树的样子,也许你会载入史册!
输入
第一行包含一个整数
t
(
1
≤
t
≤
2
⋅
1
0
4
)
t (1≤t≤2⋅10^4)
t(1≤t≤2⋅104)——测试用例的数量。然后是
t
t
t 个案例。
每个测试用例的第一行包含两个整数, n n n 和 m ( 2 ≤ n ≤ 2 ⋅ 1 0 5 ; 1 ≤ m ≤ 2 ⋅ 1 0 5 ) m(2≤n≤2⋅10^5;1≤m≤2⋅10^5) m(2≤n≤2⋅105;1≤m≤2⋅105)——分别是树的大小和精灵的数量。
每个测试用例接下来的 n-1 行每行都包含三个整数,x、y 和 v(1≤x,y≤n;-1≤v<230)——这意味着在节点 x 和 y 之间有一条边。如果
v=−1
:圣诞老人不记得这条边的灯组是什么。
v≥0
:边缘上的灯组为 v。
每个测试用例接下来的 m 行每行都包含三个整数,
a
、
b
a、b
a、b 和
p
(
1
≤
a
,
b
≤
n
;
a
≠
b
;
0
≤
p
≤
1
)
p(1≤a,b≤n;a≠b;0≤p≤1)
p(1≤a,b≤n;a=b;0≤p≤1)——精灵被分配到的节点,以及精灵最喜欢的数字中设置位数的奇偶校验。
保证所有 n 的总和和所有 m 的总和各不超过 2 ⋅ 1 0 5 2⋅10^5 2⋅105。
保证给定的边形成一棵树。
输出
对于每个测试用例,首先打印 YES
或 NO
(在任何情况下),无论是否有与圣诞老人记忆一致的树。
如果答案是 YES,则打印 n-1 行,每行包含三个整数: x 、 y x、y x、y 和 v ( 1 ≤ x , y ≤ n ; 0 ≤ v < 2 30 ) v (1≤x,y≤n; 0≤v<2^{30}) v(1≤x,y≤n;0≤v<230) — 边和该边上的整数。边的集合必须与输入中的相同,如果某个边的值之前指定,则不能更改。您可以按任何顺序打印边缘。
如果有多个答案,打印任何一个。
题解:
令 c o u n t ( x ) count(x) count(x) 为整数 x x x 中 1 1 1 位的数量。注意 c o u n t ( x ⊕ y ) m o d 2 = c o u n t ( x ) m o d 2 ⊕ c o u n t ( y ) m o d 2 count(x⊕y) mod 2 = count(x) mod 2 ⊕ count(y) mod 2 count(x⊕y)mod2=count(x)mod2⊕count(y)mod2。这意味着您可以用 count(x)mod2 替换树上的每个整数 x x x。请注意,您可以假设初始给定的边也只是在仅由该边组成的路径上行驶的精灵。在这个转换之后,每条边的权重要么是 0 0 0,要么是 1 1 1,你会得到一组路径,你会被告知每条路径的 X O R XOR XOR。
将节点 1 1 1 处的树作为根。令 r i r_i ri 为从节点 i i i 到根节点 ( r 1 = 0 ) (r_1=0) (r1=0) 边上的值的异或。请注意,路径 ( a , b ) (a,b) (a,b) 的 X O R XOR XOR 是 r a ⊕ r b r_a⊕r_b ra⊕rb。由此, ( a , b , c ) (a,b,c) (a,b,c) 形式的每个约束告诉您路径 ( a , b ) (a,b) (a,b) 的 X O R XOR XOR 必须等于 c c c 等价于 r a ⊕ r b = c r_a⊕r_b=c ra⊕rb=c。
这个问题可以使用二分着色的变体来解决,您可以在其中创建一个图形并在 ( a , b ) (a,b) (a,b) 之间添加一个双向边,每个约束的权重为 c c c。您通过每个单独的连接组件运行 d f s dfs dfs。在一个组件中,选择单个节点的值唯一地决定了其余的。在 d f s dfs dfs 期间,如果您在节点 a a a 处并正在考虑遍历边 ( a , b , c ) (a,b,c) (a,b,c),则您知道 r b = r a ⊕ c r_b=r_a⊕c rb=ra⊕c,因此您可以从 r a r_a ra 确定 r b r_b rb。
a 和 p(a 的父级)之间的边的最终值是 r a ⊕ r p r_a⊕r_p ra⊕rp。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
const int maxn = 2e5 + 10;
int T, n, m;
struct road
{
int u, v, w;
}tree[maxn];
struct graph
{
int tot, head[maxn], to[maxn<<2], nxt[maxn<<2], val[maxn<<2];
inline void add_edge(int u, int v, int w)
{
tot++;
to[tot] = v;
nxt[tot] = head[u];
val[tot] = w;
head[u] = tot;
}
}graph;
bool flag;
bool vis[maxn];
int r_xor[maxn];
void dfs_graph(int u)
{
vis[u] = 1;
for(int r = graph.head[u]; r; r = graph.nxt[r])
{
if(vis[graph.to[r]])
{
if(graph.val[r] == r_xor[u] ^ r_xor[graph.to[r]]) continue;
flag = 1;
return;
}
else
{
r_xor[graph.to[r]] = r_xor[u] ^ graph.val[r];
dfs_graph(graph.to[r]);
if(flag) return;
}
}
}
inline int count(int x)
{
int cnt = 0;
while(x)
{
if(x & 1) cnt++;
x >>= 1;
}
return cnt;
}
void solve(){
scanf("%d%d", &n, &m);
flag = graph.tot = 0;
for(int i = 1; i <= n; i++)
vis[i] = graph.head[i] = 0;
for(int i = 1; i < n; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
tree[i].u = u;
tree[i].v = v;
tree[i].w = w;
if(w != -1)
{
int cw = count(w) % 2;
graph.add_edge(u, v, cw);
graph.add_edge(v, u, cw);
}
}
for(int i = 0; i < m; i++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
graph.add_edge(u, v, w);
graph.add_edge(v, u, w);
}
for(int i = 1; i <= n; i++)
{
if(!vis[i]) dfs_graph(i);
if(flag) break;
}
if(flag)
{
printf("NO\n");
return;
}
printf("YES\n");
for(int i = 1; i < n; i++)
{
if(tree[i].w == -1)
tree[i].w = r_xor[tree[i].u] ^ r_xor[tree[i].v];
printf("%d %d %d\n", tree[i].u, tree[i].v, tree[i].w);
}
}
int main(){
ios::sync_with_stdio(0);
int t;
cin>>t;
while(t--)
solve();
return 0;
}