题意是给你一个无向图,让你求至少要放多少个太平井,使得图中任意一个点坏掉,在其它所有点的人都能通过安全井逃生。
显然先求出BCC(点双)。
显然把井放在割顶不合适,因为如果割顶挂了,这个放在割顶的井不能帮助任何人逃生。
然后对于一个BCC,如果它只有一个割顶,那么一定要在非割顶处放一口井,因为如果割顶挂了,剩下的人就和大部队分离开来,他们需要一口井,这口井放在BCC中除了割顶的任意一个点都可以。
如果一个BCC有两个割顶,那么就无关紧要了。因为题目中说,最多只会有一个点被破坏,所以如果有两个割顶的话,一个坏了,剩下的人还可以通过另一个跑掉。
所以做法就是跑一遍tarjan求出所有的BCC,然后对于只有一个割顶的BCC,要在BCC中除了割顶的任意一点放置一口井,对于两个或以上的,不用放井。
BCC中若无割顶,则说明整个图是个BCC。
否则BCC中一定有一个割顶。(这句话可以换一个表述,两个BCC之间一定是由割顶相连,如果没有割顶相连,则这两个BCC可以合并为一个割顶)
最后乘法原理一波带走。
#include <cstdio> #include <cstring> #include <algorithm> #include <stack> #include <vector> #define LL long long using namespace std; const LL maxn = 5 * 1e4 + 5, maxm = maxn * 2; LL N, n, tot, dfs_clock, bcc_cnt, ans1, ans2; LL h[maxn], low[maxn], dfn[maxn], bccno[maxn], iscut[maxn]; struct edge { LL v, next; }a[maxm]; struct EDGE { LL u, v; }; vector<LL> bcc[maxn]; stack<EDGE> s; void add(LL x, LL y) { a[tot].v = y; a[tot].next = h[x]; h[x] = tot++; } LL dfs(LL u, LL fa) { LL lowu = dfn[u] = ++dfs_clock; LL child = 0; for (LL i = h[u]; ~i; i = a[i].next) { LL v = a[i].v; EDGE e = (EDGE){u, v}; if (!dfn[v]) { s.push(e); child++; LL lowv = dfs(v, u); lowu = min(lowu, lowv); if (lowv >= dfn[u]) { iscut[u] = 1; bcc_cnt++; bcc[bcc_cnt].clear(); for(;;) { EDGE x = s.top(); s.pop(); if (bccno[x.u] != bcc_cnt) { bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt; }if (bccno[x.v] != bcc_cnt) { bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt; } if (x.u == u && x.v == v) break; } } } else if (dfn[v] < dfn[u] && v != fa) { s.push(e); lowu = min(lowu ,dfn[v]); } } if (fa == 0 && child == 1) { iscut[u] = 0; } return low[u] = lowu; } int main() { // freopen("uva5135.in","r",stdin); LL kase = 0; while (scanf("%lld", &N) && N) { memset(h, -1, sizeof h); tot = bcc_cnt = dfs_clock = 0; memset(iscut, 0, sizeof iscut); memset(dfn, 0, sizeof dfn); memset(low, 0, sizeof low); memset(bccno, 0, sizeof bccno); LL n = 0; for (LL i = 1; i <= N; i++) { LL x, y; scanf("%lld%lld", &x, &y); add(x, y); add(y, x); n = max(n, max(x, y)); } dfs(1, 0); ans1 = 0; ans2 = 1; if (bcc_cnt == 1) { ans1 = 2; ans2 = n * (n - 1) / 2; }else for (LL i = 1; i <= bcc_cnt; i++) { LL cut_cnt = 0; for (LL j = 0; j < bcc[i].size(); j++) if (iscut[bcc[i][j]]) cut_cnt++; if (cut_cnt == 1) { ans1++; ans2 *= bcc[i].size() - 1; } } printf("Case %lld: %lld %lld ", ++kase, ans1, ans2); } return 0; }
还有一个做法是先找出割顶,然后从非割顶的点开始dfs,dfs的过程中不许经过割顶。稍后补坑。