「AtCoder Beginner Contest 209 E」题解

Description

Link


题意:给定 \(n\) 个单词,一个单词的后三位若和另一个单词的前三位相同,则可以连一条有向边,问每次从第 \(i\) 个单词开始,两人交替说出单词,说不出的人输,问每次的结果,\(1\leq n\leq2\times10^5\)
Solution:
大体思路便是建返图后倒推每个点的状态,不过实现有一些细节。
整篇题解中:
-1 表示平手。
0 表示必败。
1 表示必胜。
图均为反图
又因为是反图,所以胜负均为下一个人的角度。

连边

暴力连边的时间复杂度是 \(\Theta(n^2)\),我们需要改变连边方式。

我最开先的思路是建中转点,但这样会让之后树上的操作很麻烦,题解的方法是自连。

比如:abcd 和 bcda。设 bcd 为 1,abc 为 2,cda 为 3。按照自连,我们得到以下这张图。


原本的连边应该是\(<2,1>\) \(\rightarrow\) \(<1,3>\),所以当前图为反图。又因为 abcd 的尾和 bcda 的头相同,所以这两个点实则是一个。这样连边的复杂度降到了 \(\Theta(n)\)。不过保存答案时要把 \(i\) 当做 \(i\) 的后三个字符进行操作。
比如上面这张图,\(ans[2]=0\)\(ans[1]=1\)\(ans[3]=0\),把abcd的答案设为 \(ans[1]\),bcda的答案当做 \(ans[3]\),与直接两者相连的答案相同。以此类推。
Code:

    for(int i=1,x,y;i<=n;i++)  
    {
	cin>>s;
	int len=s.length();
	string tmp;
	tmp=s.substr(0,3);
	if(!mp[tmp])
	{
		mp[tmp]=++cnt;
	}
	x=mp[tmp];//前三个。 
	tmp.clear();
	tmp=s.substr(len-3,3);
	if(!mp[tmp])
	{
		mp[tmp]=++cnt;
	}
	y=mp[tmp];//后三个。 
	in[x]++;
	add(y,x);//这两行是在建反图。
	vis[i]=y;//用来转移答案。
  }

转移

一个比较显然的地方是选择的奇偶会影响结果。

设当前点为 \(i\),下一个点为 \(v\),初始所有点都为平局,0入度的点为必败,括号内的数字表示图中的点。
请看下面这幅图:

在这里插入图片描述

Takahashi 下 1,Aoki 若下 4,Takahashi 败;Aoki 下 2,Takahashi胜。但 Aoki 很明显会选择下 4。

由此我们得出最短链的奇偶性决定结果。这就解决了多入度的胜负情况。

再考虑上环,如下图:

\(ans[1]\)=0,\(ans[2]=1\),此时入环,环内其他点均不能由2转移到,后手必定会在环上跑。
也就是说环上的点(2)除直接与环外的点(1)连接的那些点外,其他均为平局。
既然我不进环,从环出去的那些点(5)(正图是入环)也不会被更新,故会保持平局。
怎么入环退出呢?这个好办,判断入入度个数就行。
所以只有 \(ans[i]\) 为 1 或 0,且 \(v\) 不在环内时可以更新。

Code:

    int now=q.front();
    q.pop();
    for(int i=0;i<vv[now].size();i++)  
    {
	int v=vv[now][i]; 
	if(ans[v]!=-1) continue;//胜 负 已 分------->拥有最短链的人获胜。 
	in[v]--;
	if(!ans[now])//当前为败。
	{
		ans[v]=1;
		q.push(v);
		continue; 
	}
	if(!in[v])//否则便是当前为胜,且避免了卡环。 
	{
		ans[v]=0;q.push(v);
	}
  }

注意存的是拓扑序,所以用 \(\operatorname{vector}\) 存边。
手写队列挂了。。。

完整代码

#include<iostream>
#include<cstring>
#include<map>
#include<vector>
#include<queue>
using namespace std;
map<string,int> mp;
const int MAXN=2e5+5;
int n,in[MAXN<<1],head[MAXN<<1],ans[MAXN<<1],cnt;//-1为平,0为败,1为胜 ------>这里胜负都是指后手的胜负,因为连的反向边。 
int vis[MAXN<<1];//实在想不过来画画图~ 
string s;
struct ren{
	int to,next;
}a[MAXN<<1];
vector<int> vv[MAXN];
void add(int x,int y)
{
	vv[x].push_back(y);
}
queue<int> q;
int main()
{
	scanf("%d",&n);	
	for(int i=1,x,y;i<=n;i++)
	{
		cin>>s;
		int len=s.length();
		string tmp;
		tmp=s.substr(0,3);
		if(!mp[tmp])
		{
			mp[tmp]=++cnt;
		}
		x=mp[tmp];//前三个。 
		tmp.clear();
		tmp=s.substr(len-3,3);
		if(!mp[tmp])
		{
			mp[tmp]=++cnt;
		}
		y=mp[tmp];//后三个。 
		in[x]++;
		add(y,x);//注意,这两行是在建反图,从而倒推得到每个点的胜负关系。
		vis[i]=y;
	}
	for(int i=1;i<=cnt;i++)
	{
		ans[i]=-1;
		if(!in[i])
		{
			q.push(i);
			ans[i]=0;
		}
	}
	while(q.size())
	{
		int now=q.front();
		q.pop();
		for(int i=0;i<vv[now].size();i++)
		{
			int v=vv[now][i]; 
			if(ans[v]!=-1) continue;//胜 负 已 分------->拥有最短链的人获胜。 
			in[v]--;
			if(!ans[now])
			{
				ans[v]=1;
				q.push(v);
				continue; 
			}
			if(!in[v])//避免了卡环。 
			{
				ans[v]=0;q.push(v);
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(!ans[vis[i]]) printf("Takahashi\n"); 
		if(ans[vis[i]]==1) printf("Aoki\n");
		if(ans[vis[i]]==-1) printf("Draw\n");
	}
	return 0;
}

\(\Bbb{End.}\)
\(\Bbb{Thanks}\) \(\Bbb{For}\) \(\Bbb{Reading.}\)

posted @ 2021-07-11 14:25  StranGePants  阅读(80)  评论(0编辑  收藏  举报