可以说是很好的一道点双连通例题了
首 先 考 虑 对 于 一 个 非 割 点 , 删 去 后 图 分 成 若 干 个 连 通 块 首先考虑对于一个非割点,删去后图分成若干个连通块 首先考虑对于一个非割点,删去后图分成若干个连通块
那我们暂时把所有割点删掉,这样分成了若干个连通块
此 时 如 果 在 每 个 连 通 块 建 造 一 个 逃 生 口 肯 定 是 符 合 要 求 的 , 但 不 是 最 小 的 此时如果在每个连通块建造一个逃生口肯定是符合要求的,但不是最小的 此时如果在每个连通块建造一个逃生口肯定是符合要求的,但不是最小的
为什么肯定符合要求呢?
因 为 如 果 坍 塌 的 点 是 割 点 , 由 于 每 个 连 通 块 都 有 逃 生 , 所 以 可 行 因为如果坍塌的点是割点,由于每个连通块都有逃生,所以可行 因为如果坍塌的点是割点,由于每个连通块都有逃生,所以可行
坍 塌 的 不 是 割 点 , 那 就 是 在 点 双 连 通 中 , 坍 塌 后 连 通 块 仍 然 联 通 , 所 以 可 行 坍塌的不是割点,那就是在点双连通中,坍塌后连通块仍然联通,所以可行 坍塌的不是割点,那就是在点双连通中,坍塌后连通块仍然联通,所以可行
但是答案不是最小的
首 先 考 虑 一 个 点 双 连 通 , 没 有 割 点 和 他 相 邻 首先考虑一个点双连通,没有割点和他相邻 首先考虑一个点双连通,没有割点和他相邻
那 么 不 可 避 免 地 , 要 放 置 2 个 逃 生 口 ( 被 炸 了 一 个 还 有 一 个 ) , 逃 生 口 可 以 随 机 放 置 那么不可避免地,要放置2个逃生口(被炸了一个还有一个),逃生口可以随机放置 那么不可避免地,要放置2个逃生口(被炸了一个还有一个),逃生口可以随机放置
如 果 只 有 一 个 割 点 和 它 相 邻 , 那 么 只 需 要 放 1 个 , 但 是 不 能 放 在 割 点 上 如果只有一个割点和它相邻,那么只需要放1个,但是不能放在割点上 如果只有一个割点和它相邻,那么只需要放1个,但是不能放在割点上
因 为 有 割 点 , 说 明 还 有 另 一 个 点 双 和 这 个 割 点 相 邻 , 放 在 割 点 上 被 炸 掉 血 亏 ! ! 因为有割点,说明还有另一个点双和这个割点相邻,放在割点上被炸掉血亏!! 因为有割点,说明还有另一个点双和这个割点相邻,放在割点上被炸掉血亏!!
而 这 样 如 果 炸 的 不 是 自 己 逃 生 口 , 本 点 双 可 以 逃 生 . 如 果 炸 的 是 自 己 的 逃 生 口 而这样如果炸的不是自己逃生口,本点双可以逃生.如果炸的是自己的逃生口 而这样如果炸的不是自己逃生口,本点双可以逃生.如果炸的是自己的逃生口
那 么 我 可 以 通 过 割 点 去 其 他 点 双 找 出 口 那么我可以通过割点去其他点双找出口 那么我可以通过割点去其他点双找出口
如 果 有 2 个 或 以 上 割 点 相 邻 , 那 么 不 需 要 设 置 逃 生 口 了 如果有2个或以上割点相邻,那么不需要设置逃生口了 如果有2个或以上割点相邻,那么不需要设置逃生口了
因 为 不 管 怎 么 炸 , 我 都 可 以 通 过 没 被 炸 掉 的 割 点 出 去 找 出 口 因为不管怎么炸,我都可以通过没被炸掉的割点出去找出口 因为不管怎么炸,我都可以通过没被炸掉的割点出去找出口
#include <bits/stdc++.h> using namespace std; #define int long long const int maxn=5009; struct edge{ int to,nxt,use; }d[maxn]; int head[maxn],cnt; void add(int u,int v){d[++cnt]=(edge){v,head[u],0},head[u]=cnt; } int dfn[maxn],low[maxn],id,vis[maxn],cut[maxn],stac[maxn],top,bccnum; vector<int>bcc[maxn]; int ans1,ans2=1,n,m; void tarjan(int u,int fa) { int child=0; dfn[u]=low[u]=++id, stac[++top]=u; for(int i=head[u];i;i=d[i].nxt ) { int v=d[i].to; if( !dfn[v] ) { tarjan(v,fa); low[u]=min( low[v],low[u] ); if( low[v]>=dfn[u] ) { child++; if( u!=fa||child>=2 ) cut[u]=1; bcc[++bccnum].clear(); /*while( stac[top]!=u ) bcc[bccnum].push_back( stac[top--] );*/ int temp; while( temp=stac[top--] ) { bcc[bccnum].push_back(temp); if( temp==v ) break; } if( stac[top]!=u ) cout << "去死吧\n" << top << endl; bcc[bccnum].push_back(u);//割点不能出栈,因为可能包含在多个点双里 } } else low[u]=min( low[u],dfn[v] ); } } void init() { cnt=1,bccnum=0,id=0,top=0; ans1=0,ans2=1; for(int i=0;i<=n;i++) head[i]=low[i]=dfn[i]=cut[i]=0; n=0; } signed main() { int casenum=0; while( cin >> m && m ) { for(int i=1;i<=m;i++) { int l,r; cin >> l >> r; add(l,r); add(r,l); n=max( max(l,r),n ); } for(int i=1;i<=n;i++) if( !dfn[i] ) tarjan(i,i); for(int i=1;i<=bccnum;i++) { int cu=0,num=bcc[i].size(); for(int j=0;j<num;j++) if( cut[bcc[i][j]] ) cu++; if( !cu ) ans1+=2,ans2*=num*(num-1)/2; if( cu==1 ) ans1++,ans2*=(num-1); } printf("Case %lld: %lld %lld\n",++casenum,ans1,ans2); init(); } }