Luogu P3225 [HNOI2012]礦場搭建
阿新 • • 發佈:2020-07-26
思路
(這個題當時去qbxt的時候hzwer講過)。這個題其實涉及到一些新的知識,叫做點雙連通分量(概念很簡單,就是在一張連通的無向圖中,對於兩個點u和v,如果無論刪去哪條邊(只能刪去一條)都不
能使它們不連通,我們就說u和v邊雙連通)。
這個題就是點雙模板+組合數學。點雙模板一會兒程式碼裡會有。組合數學是統計答案的時候用的。說得這麼玄乎,其實就是Tarjan跑出割點,然後DFS跑連通塊,計算每個連通塊中的割點數目,再分類
討論即可(組合數學統計答案)。
分類討論(對於每個連通塊):
(1)沒有割點:那至少要建立兩個出口,注意是要從任意非割點的地方選擇兩個點建立。方案數為\(ans=ans\times cnt\times (cnt-1)/2\)
(2)有一個割點,那隻要在這個連通塊內任意一個非割點的地方設立一個出口即可。方案數為\(ans=ans\times cnt\)。(又是玄學的組合數學,感性理解)。
(3)如果有兩個及以上個割點,那就無需建立出口,因為無論如何都可以直接到達其他連通塊。
Code
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #define MAXN 505 typedef long long ll; int n, m, rt, kid, cnt; int dfn[MAXN], low[MAXN]; int vis[MAXN]; bool cut[MAXN],flag[MAXN]; int num, tot, res1, T, cs; ll res2; class node{ public: int to; node *nxt=NULL; } edge[MAXN << 1], *head[MAXN]; class Stack{ private: int stk[MAXN], top; public: inline void Push(int x) { stk[++top] = x; return; } inline void Pop(void) { --top; return; } inline int Top(void) { return stk[top]; } } Stk;//這裡習慣性加上了個棧,但是沒用 inline int read(void){ int f = 1, x = 0;char ch; do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9'); do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9'); return f * x; } inline int _min(int x,int y) { return x < y ? x : y; } inline int _max(int x,int y) { return x > y ? x : y; } inline void _init(void){ memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(cut, 0, sizeof(cut)); memset(vis, 0, sizeof(vis)); cnt = tot = n = res1 = T = 0; res2 = 1;//有乘法,所以初始化是1 return; }//多組資料千萬不要忘了初始化。 inline void add_edge(int x,int y){ ++cnt; edge[cnt].nxt = head[x]; head[x] = &edge[cnt]; head[x]->to = y; return; } void tarjan(int k,int fa){ dfn[k] = low[k] = ++tot; for (node *i = head[k]; i != NULL; i = i->nxt){ int v = i->to; if (!dfn[v]){ tarjan(v, k); low[k] = _min(low[k], low[v]); if (low[v]>=dfn[k]){ if(k==rt) ++kid; else cut[k] = 1; } } else if(v!=fa) low[k] = _min(low[k], dfn[v]); } return; }//tarjan求割點 void dfs(int x){ vis[x] = T;//為了省去每次memset的時間耗費,這裡使用時間戳 if(cut[x]) return; ++cnt; for (node *i = head[x]; i != NULL; i = i->nxt){ int v = i->to; if(cut[v]&&vis[v]!=T) ++num, vis[v] = T; if(!vis[v]) dfs(v); } }//DFS找連通塊 int main(){ m=read(); while (m){ _init();//初始化 for (int i = 1; i <= m; i++){ int u = read(), v = read(); n = _max(n, _max(u, v));//別忘了這個 add_edge(u, v), add_edge(v, u); } for (int i = 1; i <= n; i++){ if (!dfn[i]) tarjan(rt = i,0); if (kid>=2) cut[rt] = 1;//別忘了判斷在搜尋樹中兒子數量>=2的情況,這裡寫在外面了 kid = 0;//別忘了清空 } for (int i = 1; i <= n; i++){ if (!vis[i]&&!cut[i]){ ++T, cnt = num = 0;//清空,時間戳++ dfs(i); if (!num) res1 += 2, res2 *= cnt * (cnt - 1) / 2;//分類討論1 if (num==1) ++res1, res2 *= cnt;//分類討論2 } } printf("Case %d: %d %lld\n",++cs,res1,res2); m=read(); } return 0; }