P9150 郵箱題

彬彬冰激凌發表於2024-10-13

P9150 郵箱題

Alex_Wei 做法妙。

思路

首先我們可以建出兩張圖,一張是按照題目的要求形成的有向圖,一張是由有向邊 \((i,k_i)\) 形成的鑰匙圖。

在鑰匙圖中,每個點有且僅有一入度一出度,其形成了若干個環。

考慮當前點 \(i\),模擬題目過程不斷跳點,跳出的序列為 \(a=\{i,k_i,k_{k_i},\dots,j \}\)。對於 \(k_j\) 的可到達性我們進行探究。

  1. \(k_j \neq i\)
  2. 其次存在一個 \(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\) 的答案已經求出,那麼鑰匙圖上會形成若干條鏈,表示鏈頭開始可以走到鏈尾;原圖上是若干個子圖,這些子圖間可能有邊相連,但由於根據題目條件這兩個子圖互不可達,這裡仍然認為是兩個子圖;也就是一個子圖對應一條鑰匙圖上的鏈。

對子圖中的強連通分量進行縮點,發現最終縮成了一條鏈。

下文不加特殊說明,均有以下幾點約定:

  1. 鏈均指子圖縮點形成的鏈。

  2. \(c_u\to c_v\) 表示用 \(c_u\sim c_v\) 中的點形成的子圖縮點後形成的鏈。

  3. 單個 \(c_u\) 仍表示一個破環為鏈後 \(c\) 序列的一個點。

  4. \(c_i\) 視為新加入的點。

  5. \(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();
}

相關文章