【訓練題】強連通分量縮點 P1679
Description
有 N 個人和每個人所認識人的列表,注意:即使B在A的列表中,A也不一定在B的列表中。現在小明有一個重要訊息要通知這N個人,注意:如果A認識B,則當A得到這個訊息,他就會立即通知B。
現在請你完成下面兩個任務:
任務1:請你計算要讓N個人都得到訊息,那麼小明必須把這個訊息直接通知的人的最少數目。
任務2:如果小明想要只告訴這N個人中的任何一個人,其他所有人都能得到訊息,那麼可能需要在某些人的認識列表新增認識的新成員。請你計算,最少新增多少新成員,就可以讓任何一個人得到訊息,都能傳到其他所有人。
Input
第一行包括一個整數 N:表示人數,這些人編號依次為1..N。接下來 N 行中每行都表示一個認識關係的列表,第 i+1 行包括表示第 i 人認識的人的列表,每個列表用 0 結束,空列表只用一個 0 表示。
Output
第一行包括一個正整數:任務 1 的解。第二行應該包括任務2 的解。
Hint
N<=10000
Solution
首先這道題要用tarjan縮點成一個DAG圖,之後統計入度和出度為1的點,這些點就需要一條邊來形成環。邊的數量取入度和出度為1的點的數量的最大值就可以了因為加1條邊可以分別處理一個入度為1和出度為1的點,單獨的點就需要單獨一條邊來處理。tarjan找強連通分量的原理是把點壓進棧裡進行dfs,對dfn值為0的點進行tarjan更新dfn這個時間戳,把low設定為u和v裡面low值最小的,然後當v還沒有屬於任何一個強連通分量的時候,把s的low值更新成為low[u]和dfn[v]裡的最小值。如果他們屬於同一個連通分量他們的low值都是祖先結點的low值。這個過程類似於動態規劃。當找到一條返祖邊,也就是low[s]==dfn[s]的時候,就把在s之前的點全部彈出並記錄在一個強連通分量裡直到把s彈出來。
由於圖不一定連通而一個孤點也是一個強連通分量所以只要!dfn[i]就要進行tarjan。最後列舉所有邊,要是有不在同一個強連通分量的兩個點之間有邊,起點這個強連通分量的出度就++,終點的強連通分量的入度就++。然後列舉縮點後的所有強連通分量,用ans1和ans2記錄入度為0和出度為0的點的數量,就完了。
注意事項:
1、如果所有的點都在一個強連通分量裡面,就不需要加邊,所以if(cnt==1)ans=0這個語句是必要的。
#include<cstdio> #include<iostream> #include<cstring> #include<stack> #include<algorithm> #define maxn 1000010 using namespace std; struct Edge{ int u; int v; int next; }edge[maxn]; stack<int>stk; int first[maxn],last[maxn],low[maxn],dfn[maxn],belong[maxn],rd[maxn],cd[maxn]; int node,n,x,hhhh,cnt,ans1,ans2,ans; void addedge(int u,int v){ edge[++node]=(Edge){u,v,0}; if(first[u]==0)first[u]=node; else edge[last[u]].next=node; last[u]=node; return; } void init(){ scanf("%d",&n); for(int i=1;i<=n;i++){ while(1){ scanf("%d",&x); if(x==0)break; addedge(i,x); } } return; } void tarjan(int s){ low[s]=dfn[s]=++hhhh; stk.push(s); for(int q=first[s];q;q=edge[q].next){ int p=edge[q].v; if(dfn[p]==0){ tarjan(p); low[s]=min(low[s],low[p]); } else if(!belong[p]){ low[s]=min(low[s],dfn[p]); } } if(low[s]==dfn[s]){ cnt++; belong[s]=cnt; while(stk.top()!=s){ belong[stk.top()]=cnt; stk.pop(); } stk.pop(); } } void solve(){ for(int i=1;i<=n;i++){ if(!dfn[i]){ tarjan(i); } } for(int i=1;i<=n;i++){ for(int q=first[i];q;q=edge[q].next){ int p=edge[q].v; if(belong[i]!=belong[p]){ rd[belong[p]]++; cd[belong[i]]++; } } } for(int i=1;i<=cnt;i++){ if(rd[i]==0)ans1++; if(cd[i]==0)ans2++; } } int main(){ init(); solve(); printf("%d\n",ans1); ans=max(ans1,ans2); if(cnt==1)ans=0; printf("%d\n",ans); return 0; }