noip2019集训测试赛(二十一)Problem B: 红蓝树
noip2019集训测试赛(二十一)Problem B: 红蓝树
Description
有一棵N个点,顶点标号为1到N的树。N−1条边中的第i条边连接顶点ai和bi。
每条边在初始时被染成蓝色。高桥君将进行N−1次操作,来把这棵蓝色的树变成红色的树。
* 选一条仅包含蓝色边的简单路径,并删除这些边中的一条。
* 然后在路径的两个端点中间连一条红色的边。
他的目标是,对于每一个i,都有一条红色的边连接ci和di。
现在请你判断是否可能达成他的目标。
每条边在初始时被染成蓝色。高桥君将进行N−1次操作,来把这棵蓝色的树变成红色的树。
* 选一条仅包含蓝色边的简单路径,并删除这些边中的一条。
* 然后在路径的两个端点中间连一条红色的边。
他的目标是,对于每一个i,都有一条红色的边连接ci和di。
现在请你判断是否可能达成他的目标。
Input
题目数据按一下格式从标准输入输出输入:
NN
a1 b1
⋮
aN−1 bN−1
c1 d1
⋮
cN−1 dN−1
NN
a1 b1
⋮
aN−1 bN−1
c1 d1
⋮
cN−1 dN−1
Output
如果目标可以达成,输出`YES`,否则输出`NO`。
Sample Input
sample input 1:
3
1 2
2 3
1 3
3 2
sample input 2:
5
1 2
2 3
3 4
4 5
3 4
2 4
1 4
1 5
sample input 3:
6
1 2
3 5
4 6
1 6
5 1
5 3
1 4
2 6
4 3
5 6
Sample Output
sample output 1:
YES
sample output 2:
YES
sample output 3:
NO
HINT
样例 1 :
目标可以达成:* 首先,选择连接顶点1和33的边,并移除1到2之间的蓝色边,并在1和3之间生成一条红色边;
* 然后,选择连接顶点2和3的边,并删去2和3之间的蓝色边,并在2和3之间生成一条红色边。
* 2≤N≤105
* 1≤ai,bi,ci,di≤N
* ai≠bi
* ci≠di
* 输入的两个图都是树。
解析:
如果可以做到将红树变为蓝树,那么在改变完N-2条边后,最后的没有连上一条蓝边必与最后一条红边重合
那么这一条重合的边可以在以前所有的连边中任意使用
那么我们可以对于原树中每一条既是红的又是蓝的边的两个端点缩成一个点(删去原本连接他们的边,再把其中一个点的所有边转到另一个点上,用启发式合并来缩点)
如果最终整棵树能够缩成一个点,那么就是YES,否则就是NO
启发式合并的总耗时是O(nlogn)
所以整个算法的时间复杂度是O(nlogn)
#include<iostream> #include<cstdio> #include<set> using namespace std; struct data{ int x,y; }t[200001]; int n,m,f[200001],x,y,cnt; set<int> v[200001]; int fa(int a){ if(f[a]!=a)f[a]=fa(f[a]); return f[a]; } void ins(int a,int b){ set<int>::iterator id1=v[a].find(b),id2=v[b].find(a); if(id1==v[a].end()){ v[a].insert(b); v[b].insert(a); }else{ cnt++; v[a].erase(b); v[b].erase(a); t[cnt].x=a; t[cnt].y=b; } } int main(){ scanf("%d",&n); for(int i=1;i<=n*2-2;i++){ scanf("%d%d",&x,&y); ins(x,y); } for(int i=1;i<=n;i++)f[i]=i; while(cnt){ x=fa(t[cnt].x); y=fa(t[cnt].y); cnt--; if(v[x].size()>v[y].size())swap(x,y); f[x]=y; for(int i:v[x]){ v[i].erase(x); ins(i,y); } v[x].clear(); m++; } if(m==n-1)printf("YES\n"); else printf("NO\n"); }