1. 程式人生 > 其它 >[題解]AtCoder Beginner Contest 209 E

[題解]AtCoder Beginner Contest 209 E

Description

Link


題意:給定 \(n\) 個單詞,一個單詞的後三位若和另一個單詞的前三位相同,則可以連一條有向邊,問每次從第 \(i\) 個單詞開始,兩人交替說出單詞,說不出的人輸,問每次的結果,\(1\leq n\leq2\times10^5\)
Solution:
大體思路便是建返圖後倒推每個點的狀態,不過實現有一些細節。
整篇題解中:
-1表示平手。
0表示必敗。
1表示必勝。
因為建的返圖,所以勝負均為下一個人的角度。

連邊

暴力連邊的時間複雜度是 \(\omicron(n^2)\),我們需要改變連邊方式。

我最開先的思路是建中轉點,但這樣會讓之後樹上的操作很麻煩,題解的方法是自連。

比如:abcd和bcda。設bcd為1,abc為2,cda為3。按照自連,我們得到以下這張圖。


原本的連邊應該是\(<2,1>\) \(\rightarrow\) \(<1,3>\),因為abcd的尾和bcda的頭相同,所以這兩個點實則是一個。這樣連邊的複雜度降到了 \(\omicron(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[5]\)=0,\(ans[4]=1\),此時入環,環內其他點均不能由4轉移到,因為 \(i=4\) 時,下一個人必定走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);
	}
  }

注意存的是top序,所以用 \(\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;
}//冰汽時代