题目大意
给定一棵树,以及每个节点上都有的一个翻转棋的初始状态,每次操作可以取走一枚白色朝上的棋子并将所有相邻的棋子翻转,求可以取走所有棋子的字典序最小的序列。如果不能取走所有棋子,则输出-1。
题解
我们可以先考虑叶节点的父亲点 u u u和它的各个儿子。如果这个儿子是白色,则肯定要先取走这个儿子,不然父亲删除后这个儿子变成黑的之后就删除不了了。注意一个儿子取走后 u u u要变一次颜色。所有白色的儿子删除后,此时只剩下 u u u和黑色的儿子。
- 如果 u u u是白色,则取走 u u u,其儿子都变为白色,都能取走
- 如果 u u u是黑色,则可以等 u u u的父亲取走后, u u u变白色,就可以用上述方法将所有点取走
也就是说,在遍历完儿子的时候,先取走所有白色的儿子,然后取走父亲,在取走黑色儿子。因为儿子那边的顺序不影响你和儿子的顺序,所以我们可以知道父亲和儿子的相对顺序。
知道相对顺序,我们就可以用拓扑排序来求解。注意要字典序最小,所以要用优先队列。
当然,如果你发现当前节点为黑色且没有父亲(即当前节点为根节点),则无解,输出-1。
时间复杂度为 O ( n log n ) O(n\log n) O(nlogn)。
code
#include<bits/stdc++.h>
using namespace std;
int n,x,y,fl=0,a[200005],ct[200005],ans[200005];
int tot1=0,tot2=0,d1[400005],l1[400005],r1[400005],d2[400005],l2[400005],r2[400005];
char ch;
priority_queue<int>q;
void add1(int xx,int yy){
l1[++tot1]=r1[xx];d1[tot1]=yy;r1[xx]=tot1;
}
void add2(int xx,int yy){
l2[++tot2]=r2[xx];d2[tot2]=yy;r2[xx]=tot2;
++ct[yy];
}
void dfs(int u,int fa){
for(int i=r1[u];i;i=l1[i]){
if(d1[i]==fa) continue;
dfs(d1[i],u);
if(a[d1[i]]==0){
add2(d1[i],u);a[u]^=1;
}
else add2(u,d1[i]);
}
if(u==1&&a[u]) fl=1;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d",&x,&y);
add1(x,y);add1(y,x);
}
for(int i=1;i<=n;i++){
ch=getchar();
while(ch!='W'&&ch!='B') ch=getchar();
if(ch=='B') a[i]=1;
}
dfs(1,0);
for(int i=1;i<=n;i++){
if(!ct[i]) q.push(-i);
}
while(!q.empty()){
int u=-q.top();q.pop();
ans[++ans[0]]=u;
for(int i=r2[u];i;i=l2[i]){
--ct[d2[i]];
if(!ct[d2[i]]) q.push(-d2[i]);
}
}
if(fl){
printf("-1");return 0;
}
for(int i=1;i<=ans[0];i++){
printf("%d ",ans[i]);
}
return 0;
}