题面
https://www.luogu.org/problem/P3225
题解
首先先对割点这个东西有一个形象的理解。
一个割点就是两个或多个点双的交,所以不能把它理解成“缩点后形成的树”上的边。
也可以理解成是一个点双的边界且不能是图的边界,所以我认为割点并不是很优美。
两个点双之间,最多只有一个割点(要不然就不叫割点了)
割点和桥的对比:
点双要求严格,所以点双都较小,所以割点好找。($low[y]>=dfn[x]$)(说一句,在此过程中,父边能不能更新$low[x]$是不影响结果的)
边双要求不严格,所以边双比较大,所以割边难找。($low[y]>dfn[x]$)(此过程中,显然不能更新)
再说割点的求法(多年不写我早已遗忘)
当$low[y]>=dfn[x]$时,应该弹栈,直到弹到$y$,再把$x$“算到”这个点双里面(如果弹栈到$x$,先搜的下面的已经判定和$x$形成一个点双的部分,就被划归为这个点双里了,但其实,这些部分应该和$x$和$x$上面的点是一个点双里的)
这样求点双是没有要特判的部分的。
但是求割点的时候,由于不能是边界上的点,所以当$x$是根时,要多维护一个$rs$,记录$x$的不相交的儿子总共有多少个,如果$rs<=1$,就出现了乌龙,$x$不是割点,因为$x$没有上面的部分。
回到我们这题
我们把原图中的一个点双建一个点,一个割点建一个点,一个割点向它所在的点双们连边,这样就形成了一颗树,并且割点所代表的点,他们的度数不为$1$,如果看做无根树 ,他们一定不在边界上,在边界上的点一定是点双代表的点。
我们只考虑在非割点上建出口的情况,对于度为$1$的代表点双的点,我们必须在其中任意一个非割点建一个出口。
对于度$>=2$的代表点双的点,我们可以通过它走到其他叶子点上,所以不用建。
还有一种情况是特殊考虑的,就是整个联通块缩成一个孤立的点,也就是没有割点,那我们任选两个建出口(一个塌了走另外一个)
那如果考虑,可以在割点上建出口,答案会不会更优呢?
本来度数就$>=2$的点不需要建,我们不考虑。
叶子点双是必须建的,如果我们在割点上建,可以一个当多个使用啊(滑稽),但其实这样是错误的。因为如果一个叶子点双,只在割点上建,那么割点塌了就没的逃了。。。。。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<vector> #include<stack> #define LL long long #define N 550 #define ri register int using namespace std; int dfn[N],low[N],cut[N]; int n,num,ctc,cc,T; vector<int> to[N],c1[N],c2[N]; stack<int> s; LL ans1,ans2; void add_edge(int u,int v) { to[u].push_back(v); to[v].push_back(u); } void tarjan(int x,int ff) { dfn[x]=low[x]=++cc; s.push(x); int rs=0; for (ri i=0;i<to[x].size();i++) { int y=to[x][i]; if (!dfn[y]) { tarjan(y,x); low[x]=min(low[x],low[y]); if (low[y]>=dfn[x]) { rs++; cut[x]=1; int t; num=ctc=0; while (1) { t=s.top(); s.pop(); if (cut[t]) ++ctc; else ++num; if (t==y) break; } if (cut[x]) ++ctc; else ++num; c1[x].push_back(ctc); c2[x].push_back(num); } } else low[x]=min(low[x],dfn[y]); } if (x==ff && rs<=1) { for (ri i=0;i<c1[x].size();i++) c1[x][i]--,c2[x][i]++; } for (ri i=0;i<c1[x].size();i++) { if (c1[x][i]==0) { ans1+=2; ans2*=((c2[x][i]-1)*c2[x][i])/2; } else if (c1[x][i]==1) { ans1++; ans2*=c2[x][i]; } } } int main() { freopen("mine1.in","r",stdin); int m,u,v; while (scanf("%d",&m)==1 && m) { memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(cut,0,sizeof(cut)); for (ri i=1;i<=n;i++) to[i].clear(),c1[i].clear(),c2[i].clear(); while (!s.empty()) s.pop(); n=0; cc=0; ans1=0; ans2=1; for (ri i=1;i<=m;i++) { scanf("%d %d",&u,&v); add_edge(u,v); n=max(n,u); n=max(n,v); } for (ri i=1;i<=n;i++) if (!dfn[i]) tarjan(i,i); printf("Case %d: %lld %lld ",++T,ans1,ans2); } return 0; }