ARC143E Reversi 题解

ARC143E Reversi

洛谷 ARC143E Reversi

题目大意

给定一棵树,以及每个节点上都有的一个翻转棋的初始状态,每次操作可以取走一枚白色朝上的棋子并将所有相邻的棋子翻转,求可以取走所有棋子的字典序最小的序列。如果不能取走所有棋子,则输出-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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值