P9150 郵箱題
Alex_Wei 做法妙。
思路
首先我們可以建出兩張圖,一張是按照題目的要求形成的有向圖,一張是由有向邊 \((i,k_i)\) 形成的鑰匙圖。
在鑰匙圖中,每個點有且僅有一入度一出度,其形成了若干個環。
考慮當前點 \(i\),模擬題目過程不斷跳點,跳出的序列為 \(a=\{i,k_i,k_{k_i},\dots,j \}\)。對於 \(k_j\) 的可到達性我們進行探究。
- \(k_j \neq i\)。
- 其次存在一個 \(p\) 使得原圖中存在邊 \((p\to k_j)\),且 \(j\) 可到達 \(p\)。
如何判定條件 2 呢?由於 \(a_i\) 可到達 \(a_{i+1}\),每次序列擴充 \(a_p=j\) 時,考慮 \(p\) 的返租邊 \(a_p\to a_q(q<p)\),則 \(a_q\) 到 \(a_p\) 之間強連通,使用並查集維護強連通塊,加入 \(k_j\) 時列舉邊 \(u\to k_j\) 找是否有 \(u\in a\) 且 \(u\) 與 \(j\) 強連通。
複雜度 \(O(nm\alpha)\)。
這麼跑肯定會炸,發現處理環的過程中會經過大量重複的點,能否利用已經求出的點的貢獻呢?
將環複製兩倍,破環為鏈,鏈上前一個點的答案就是該點的答案,如果答案統計時超過了環的長度,那麼答案就為環長。
設現在的鏈為 \(c\),如果 \(c_{i+1}\sim c_L\) 的答案已經求出,那麼鑰匙圖上會形成若干條鏈,表示鏈頭開始可以走到鏈尾;原圖上是若干個子圖,這些子圖間可能有邊相連,但由於根據題目條件這兩個子圖互不可達,這裡仍然認為是兩個子圖;也就是一個子圖對應一條鑰匙圖上的鏈。
對子圖中的強連通分量進行縮點,發現最終縮成了一條鏈。
下文不加特殊說明,均有以下幾點約定:
-
鏈均指子圖縮點形成的鏈。
-
\(c_u\to c_v\) 表示用 \(c_u\sim c_v\) 中的點形成的子圖縮點後形成的鏈。
-
單個 \(c_u\) 仍表示一個破環為鏈後 \(c\) 序列的一個點。
-
\(c_i\) 視為新加入的點。
-
點 \(c_u\) 的編號為 \(u\)。
新加入點 \(c_i\) 就相當於判斷 \(c_i\to c_i\) 能否和 \(c_{i+1}\to c_p\) 合併,合併之後再次判斷 \(c_i\to c_p\) 能否和 \(c_{p+1}\to c_{p`}\) 合併,不斷重複鏈合併,直到無法進行下一次合併。
對於鏈 \(c_i \to c_p\) 和 \(c_{p+1}\to c_{p`}\),根據樸素做法的判斷條件,找到 \(c_u\) 與 \(c_p\) 強連通且存在邊 \(c_u\to c_{p+1}\)(這裡是原圖中的有向邊),就可以合併鏈。
\(c_u\) 換一個角度找會更方便,找存在邊 \(c_u\to c_{p+1}(u\in[i,p])\) 的最大的 \(u\),判斷 \(c_u,c_p\) 是否共一個強連通分量,因為顯然如果存在 \(v<u\) 且 \(c_v,c_p\) 屬於同一個強連通分量,\(c_u,c_p\) 也屬於同一個強連通分量。
合併鏈之後,作為一條新的鏈首先要進行縮點,我們不可能列舉第二條鏈上所有的返祖邊(即滿足 \(i<j\) 且村子於原圖的邊 \(c_j\to c_i\))。一個好的想法是,對於 \(c_i \to c_p\),維護所有終點編號不小於 \(i\) 且未考慮過的返祖邊,合併時直接暴力跑第二條鏈上返祖邊的影響然後合併即可。
這樣就結束了嗎?
仔細思考合併的過程,如果 \(c_i \to c_p\) 能和 \(c_{p+1}\to c_{p`}\) 合併,一定是由於 \(c_i\) 帶來的新變化使得這兩條鏈可以合併,否則這兩條鏈在 \(c_i\) 加入之前就合併為一條鏈了。
影響鏈合併的因素只有 \(c_u\),所以說 \(c_i\) 的加入帶來了新的可以選擇的 \(c_u\),而 \(c_u\) 與 \(c_p\) 共一個強連通分量,可以證明 \(c_i\) 與 \(c_p\) 共一個強連通分量。
這樣子對於每一條鏈,維護編號最大還有未貢獻的返祖邊的節點 \(c_x\),這些返祖邊的終點 \(u\) 滿足 \(u\in c\) 且 \(i\leq k(c_k=u)\),最後把新鏈頭 \(c_i\sim c_x\) 合併為同一個點即可。
實現時建出反邊,每次用 \(c_i\) 更新後面的鏈即可。
CODE
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e6+5;
struct dsu
{
int f[maxn];
inline int fr(int u){return f[u]==u?u:f[u]=fr(f[u]);}
inline void merge(int x,int y){f[fr(y)]=fr(x);}
}ch,cy;
int n,m;
int k[maxn],val[maxn],pre[maxn],ans1[maxn],ans2[maxn],in[maxn],cyc[maxn];
vector<int>E[maxn];
bool vis[maxn];
inline void solve()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&k[i]);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
E[y].push_back(x);
}
for(int i=1;i<=n;i++)
{
if(vis[i]) continue;
int p=i,ct=0;
while(!in[p]) cyc[++ct]=p,in[p]=ct,p=k[p];
for(int j=ct*2;j;j--)
{
ch.f[j]=cy.f[j]=j;val[j]=pre[j]=0;
int id=cyc[j>ct?j-ct:j];
for(int it:E[id])
{
if(!in[it]) continue;
it=in[it];
if(it+ct<j) it+=ct;
if(it>j) it-=ct;
pre[j]=max(pre[j],it);
if(it<j) it+=ct;
if(it<=ct*2) val[ch.fr(it)]=max(val[ch.fr(it)],cy.fr(it));
}
while(1)
{
while(1)
{
int cyid=cy.fr(j),chid=ch.fr(j);
if(cyid<val[chid]) cy.merge(cyid+1,cyid);
else break;
}
int cyid=cy.fr(j),chid=ch.fr(j);
val[chid]=0;
if(chid==ct*2||cyid!=chid||pre[chid+1]<j) break;
ch.merge(chid+1,chid);
}
ans1[id]=min(ct,ch.fr(j)-j+1);
ans2[id]=min(ct,cy.fr(j)-j+1);
}
for(int j=1;j<=ct;j++) in[cyc[j]]=0,vis[cyc[j]]=1;
}
for(int i=1;i<=n;i++) printf("%d %d\n",ans1[i],ans2[i]);
for(int i=1;i<=n;i++) vis[i]=false,E[i].clear();
}
int main()
{
int _;
scanf("%d",&_);
while(_--) solve();
}