[題解]AtCoder Beginner Contest 209 E
Description
題意:給定 \(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;
}//冰汽時代