1. 程式人生 > 實用技巧 >P5676 [GZOI2017]小z玩遊戲 Tarjan+優化建圖

P5676 [GZOI2017]小z玩遊戲 Tarjan+優化建圖

題目描述




分析

一開始看到這道題,首先想到的就是建好邊後跑一個Tarjan縮點,將siz大於1的節點統計一下,輸出結果
Tarjan非常顯然易得,關鍵就是怎麼建邊
比較好想的一種思路就是列舉每一個興奮程度
對於每一個興奮程度,再將有趣程度列舉一遍
如果有趣程度是興奮程度的倍數的話,在兩個節點之間建一條有向邊
我們拿第二個樣例模擬一下,建好邊後就是下面這樣

那麼縮點後大小不為1的強連通分量只有一個,它的大小為3
那麼最終的答案就是\(3\)
但是這樣的建邊效率為\(n^2\),複雜度接受不了
所以我們考慮更優秀的建邊方法
這裡要用到的是建虛點的方法
1.建一個由 有趣程度 到 點 的邊
2.建一個由 點 到 興奮程度 的邊
3.重點:建一個興奮程度整數倍的邊
要注意的是建虛點的時候,要把遊戲的編號加上一個\(n\)


避免和原先的編號重複
然後思路就和\(n^2\)的解法一樣
至於時間複雜度,根據大佬的證明,是

程式碼

#include<bits/stdc++.h>
using namespace std;
const int maxn=2e6+5;
struct asd{
    int from,to,next;
}b[maxn];
int head[maxn],tot=1;
void ad(int aa,int bb){
    b[tot].from=aa;
    b[tot].to=bb;
    b[tot].next=head[aa];
    head[aa]=tot++;
}
int dfn[maxn],low[maxn],top,sta[maxn],dfnc,shuyu[maxn],siz[maxn],js,vis[maxn];
void tar(int xx){
    dfn[xx]=low[xx]=++dfnc;
    sta[++top]=xx;
    for(int i=head[xx];i!=-1;i=b[i].next){
        int u=b[i].to;
        if(!dfn[u]){
            tar(u);
            low[xx]=min(low[xx],low[u]);
        } else if(!shuyu[u]){
            low[xx]=min(low[xx],dfn[u]);
        }
    }
    if(low[xx]==dfn[xx]){
        js++;
        siz[js]=1;
        while(sta[top]!=xx){
            int now=sta[top--];
            shuyu[now]=js;
            siz[js]++;
            vis[now]=1;
        }
        top--;
        shuyu[xx]=js;
        if(siz[js]>1) vis[xx]=1;
    }
}
int main(){
    int t;
    scanf("%d",&t);
    while(t--){
        memset(head,-1,sizeof(head));
        memset(&b,0,sizeof(struct asd));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        tot=1,js=0,dfnc=0,top=0;
        memset(vis,0,sizeof(vis));
        memset(sta,0,sizeof(sta));
        memset(shuyu,0,sizeof(shuyu));
        memset(siz,0,sizeof(siz));
        int n;
        scanf("%d",&n);
        int mmax=0;
        for(int i=1;i<=n;i++){
            int aa;
            scanf("%d",&aa);
            ad(n+aa,i);
            mmax=max(mmax,aa);
        }
        for(int i=1;i<=n;i++){
            int aa;
            scanf("%d",&aa);
            ad(i,n+aa);
        }
        for(int i=1;i<=mmax;i++){
            for(int j=2;j*i<=mmax;j++){
                ad(n+i,n+i*j);
            }
        }
        for(int i=1;i<=n;i++){
            if(!dfn[i]) tar(i);
        }
        int ans=0;
        for(int i=1;i<=n;i++){
            if(vis[i]==1) ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}