P5676 [GZOI2017]小z玩遊戲 Tarjan+優化建圖
阿新 • • 發佈:2020-06-28
題目描述
分析
一開始看到這道題,首先想到的就是建好邊後跑一個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; }