Blockade(Bzoj1123)
Description
Byteotia城市有n個 towns m條雙向roads. 每條 road 連線 兩個不同的 towns ,沒有重複的road. 所有towns連通。
Input
輸入n<=100000 m<=500000及m條邊
Output
輸出n個數,代表如果把第i個點去掉,將有多少對點不能互通。
Sample Input
5 5
1 2
2 3
1 3
3 4
4 5
Sample Output
8
8
16
14
8
【題前話】
這題真的害人不淺啊!
本蒟蒻懷著做板子題1A的心態開啟這道題,結果經過了三個多小時,經歷了又A又RE又WA又棧溢位的程式碼,終於在機房大佬的題解和講解之下終於AC了,RE和棧溢位的原因竟是因為一個變數開了long long!必須定義時定義int,乘的時候改為long long才能A!
本蒟蒻都驚到了,果然計算機是一個玄學的學科,對萌新真的很不友好!我到現在還是不明白,如果有路過大佬能解答我的疑惑的,在下感激不盡!(具體是哪個變數我會在題外話裡寫出)
【題解】
首先,大家應該都知道,這道題是用TARJAN演算法的。(沒學過的小盆友可以去學習一發:https://www.cnblogs.com/mxrmxr/p/9715579.html)
有的人或許覺得這道題是TARJAN縮點,其實不然。這道題是用TARJAN求割點的。具體思路如下:
打一個TARJAN模板,按照DFS樹搜尋,DFS返回後,判斷當前點是不是割點,如果是的話,那麼這個點和剛才搜尋的點就是一個聯通塊。
那我們怎麼處理有多少對點呢?
其實很簡單,就是乘法原理,相信大家都學過吧。(沒學過的出門右轉去語文競賽吧 )
總體思路如下:
對於每個割點,它的兒子們肯定有至少兩個聯通塊,如果這個割點被刪除,下面的聯通塊都不會互通。我們定義ans[u]為這個點去掉後有多少點不能互通,我們在計算每個它下面的聯通塊時,把它們的大小依次乘起來(就是下面的核心程式碼,這裡看不懂就先往下翻)。然後,再加上它兒子們的聯通塊的和乘以他父親們所有點的和,(就是乘法原理啦)這樣,我們的思路就基本結束了。
上文的核心程式碼是怎麼寫的呢?如下:
if(low[v]>=dfn[u]) { ans[u]+=(ll)sum*size[v]; sum+=size[v]; }
我們定義size代表以u為根的聯通塊大小,sum表示已經遍歷的子聯通塊,當我們發現一個新的聯通塊時,要把sum的大小乘以已經遍歷的(乘法原理),就是size[v]啦。這時我們又遍歷了一個,所以sum要加上它。(就是size[v] 啦)
總程式碼如下:
#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cstdlib> using namespace std; #define MAXN 1000010 #define num ch-'0' #define ll long long void get(int &res) { char ch;bool flag=0; while(!isdigit(ch=getchar())) (ch=='-')&&(flag=true); for(res=num;isdigit(ch=getchar());res=res*10+num); (flag)&&(res=-res); } int n,m,tot=0,cnt; int hea[MAXN],nex[MAXN<<4],to[MAXN<<4]; int dfn[MAXN],low[MAXN],size[MAXN]; ll ans[MAXN<<2]; inline void add(int a,int b) { to[++tot]=b; nex[tot]=hea[a]; hea[a]=tot; } inline void tarjan(int u) { int sum=0; dfn[u]=low[u]=++cnt; size[u]=1; for(int i=hea[u];i;i=nex[i]) { int v=to[i]; if(!dfn[v]) { tarjan(v); size[u]+=size[v]; low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]) { ans[u]+=(ll)sum*size[v]; sum+=size[v]; } } else low[u]=min(low[u],dfn[v]); } ans[u]+=(ll)sum*(n-sum-1); } int main() { get(n),get(m); for(int i=1;i<=m;i++) { int a,b; get(a),get(b); add(a,b);add(b,a); } tarjan(1); for(int i=1;i<=n;i++) printf("%lld\n",(ans[i]+n-1)<<1); }
【題外話】
在寫TARJAN時,我的sum(含義如上文所講)如果int就會WA,如果long long就會RE和棧溢位,只有先定義為int,後面乘法時改為long long才能A。
本蒟蒻十分不解,還望請過路大神指點高明。