P9220 「TAOI-1」椎名真昼 题解

题目传送门 | My Blog

因为笔者最近在刷 Tarjan 题,并且刚好在看邻家天使,所以就来写此题了。

总之是细节非常多的一道分类讨论题。

题意

给定一张有向图,图中每个节点被染成了黑色和白色,Alice 和 Bob 轮流进行操作,每次选择一个点并反转其能到达的所有点的颜色(包括自身),当某一方操作结束后图中所有点都为白色则获胜,问谁能赢。

思路

有一个很显然的结论,如果二人在 \(2\) 回合内都没有必胜策略,则必定平局,因为只要一回合不能达到必胜,另一人就可以把他上一回合进行的操作重置。

首先考虑平局的情况,注意到如果一个强连通分量中有点颜色不同,则必定不可能全部翻转成白色,因为一个强连通分量中的点翻转任意一个都会导致其他的点也翻转。

考虑在什么情况下 Alice 会赢,由于要一步完成,所以 Alice 必须选定一个黑点,并且这个黑点所能到达的点包含所有的黑点,并且其中不含白点。

再来考虑什么情况下 Bob 会赢:

  1. 当整张图都是白色时,Bob 必胜,因为无论 Alice 怎么翻转,Bob 都能在下回合转回去,实现全白。
  2. 定义源点表示缩点后入度为 \(0\) 的点,当所有源点都为黑色时,只有源点数量为 \(2\) 且整个图中只有这两个点,Bob 必胜。因为如果有更多点,则 Alice 可以随意操作非源点的点,来破坏全白。
  3. 如果只有一个源点且为黑色,并且图中只剩 \(2\) 个点,那么只有存在黑(源点)->白这种情况时,Bob必胜。因为这种情况下 Alice 无论怎么翻,Bob 都能在下一步翻成全白。

然后就是要注意细节了,下面是我踩过的几个坑:

  1. 多测一定要清空,注意在提前退出的地方也要清空
  2. 写这种题目一定要时刻记好自己在干什么,不要误用变量名
  3. 判定 Bob 获胜时记得一定要判定图中是否只剩两个点了,而不是判定是否有两个源点

Code

笔者的代码很丑,因为太难调了...

重在理解思路

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=200010;
int T,n,m,cnt,tim,reach;
int scc[N],low[N],dfn[N],c[N],ind[N];
bool in[N],col[N],vis[N],bad;
vector<int> g[N],ng[N],dis;
stack<int> st;
void tarjan(int u){
	low[u]=dfn[u]=++tim;
	st.push(u);
	in[u]=1;
	for(auto v:g[u]){
		if(!dfn[v]){
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(in[v]) low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u]){
		cnt++;
		c[cnt]=-1;
		while(!st.empty()){
			int x=st.top();
			st.pop();
			in[x]=0;
			scc[x]=cnt;
			if(c[cnt]==-1) c[cnt]=col[x];
			else if(col[x]!=c[cnt]) bad=1;
			if(x==u) break;
		}
	}
}
void dfs(int u){
	if(vis[u]) return;
	if(!c[u]){
		bad=1;
		return;
	}
	vis[u]=1;
	reach++;
	for(auto v:ng[u]) dfs(v);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>T;
	while(T--){
		cin>>n>>m;
		for(int i=1;i<=n;i++) cin>>col[i];
		for(int i=1;i<=m;i++){
			int u,v;
			cin>>u>>v;
			g[u].push_back(v);
		}
		for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
		if(bad){
			cout<<"N";
			for(int i=1;i<=n;i++) g[i].clear(),scc[i]=0,dfn[i]=0,low[i]=0,c[i]=0,ind[i]=0,in[i]=0,col[i]=0,vis[i]=0;
			for(int i=1;i<=cnt;i++) ng[i].clear();
			dis.clear();
			while(!st.empty()) st.pop();
			cnt=tim=bad=0;
			continue;
		}
		bad=0;
		for(int u=1;u<=n;u++) for(auto v:g[u]) if(scc[u]!=scc[v]) ng[scc[u]].push_back(scc[v]);
		for(int i=1;i<=cnt;i++){
			sort(ng[i].begin(),ng[i].end());
			ng[i].erase(unique(ng[i].begin(),ng[i].end()),ng[i].end());
		}
		for(int i=1;i<=cnt;i++) for(auto v:ng[i]) ind[v]++;
		queue<int> q;
		for(int i=1;i<=cnt;i++) if(!ind[i]) q.push(i);
		bool win=0;
		int fu=-1,cntb=0;
		for(int i=1;i<=cnt;i++) if(c[i]) cntb++;
		while(!q.empty()){
			int u=q.front();
			q.pop();
			if(c[u]){
				fu=u;
				break;
			}
			for(auto v:ng[u]){
				ind[v]--;
				if(!ind[v]) q.push(v);
			}
		}
		if(fu!=-1){
			reach=0,bad=0;
			memset(vis,0,sizeof(vis));
			dfs(fu);
			if(!bad&&reach==cntb) win=1;
		}
		if(win){
			cout<<"A";
			for(int i=1;i<=n;i++) g[i].clear(),scc[i]=0,dfn[i]=0,low[i]=0,c[i]=0,ind[i]=0,in[i]=0,col[i]=0,vis[i]=0;
			for(int i=1;i<=cnt;i++) ng[i].clear();
			dis.clear();
			while(!st.empty()) st.pop();
			cnt=tim=bad=0;
			continue;
		}
		memset(ind,0,sizeof(ind));
		for(int i=1;i<=cnt;i++) for(auto v:ng[i]) ind[v]++;
		for(int i=1;i<=cnt;i++) if(!ind[i]) dis.push_back(i);
		win=0;
		bool white=0;
		for(auto i:dis) if(!c[i]) white=1;
		if(!white){
			if(cnt==2) win=1;
			if(dis.size()==1&&cnt==2&&!c[ng[dis[0]][0]]) win=1;
		}
		else{
			bool aw=0;
			for(int i=1;i<=cnt;i++) if(c[i]) aw=1;
			if(!aw) win=1;
		}
		if(!win) cout<<"N";
		else cout<<"B";
		for(int i=1;i<=n;i++) g[i].clear(),scc[i]=0,dfn[i]=0,low[i]=0,c[i]=0,ind[i]=0,in[i]=0,col[i]=0,vis[i]=0;
		for(int i=1;i<=cnt;i++) ng[i].clear();
		dis.clear();
		while(!st.empty()) st.pop();
		cnt=tim=bad=0;
	}
	return 0;
}

完结撒花

posted @ 2026-10-06 10:49  .knot  阅读(22)  评论(0)    收藏  举报