problem
给出一个无向图,你可以将其中若干个点设成特殊点,满足,删掉图中任意一个点后,其他点都与至少一个特殊点连通。
请求出至少需要多少个特殊点,以及在最小化特殊点数时的方案数。
数据范围:边数 m ≤ 500 m\le500 m≤500。
solution
感觉之前写的好水啊,重新写一下。
看到这种连通性问题,应该能自然而然地想到 Tarjan 算法。
对于一个点双,那么有这几种情况(设非割点数为 c n t cnt cnt):
- 没有割点。那么任意选 2 2 2 个设为特殊点即可(不能只选 1 1 1 个,因为选的那个被毁就 g g gg gg 了),方案数是 c n t ( c n t − 1 ) 2 \frac{cnt(cnt-1)}2 2cnt(cnt−1)。
- 有 1 1 1 个割点。任意选 1 1 1 个点即可,如果它被毁,还可以从割点走到另一个点双去。
- 有 2 2 2 个及以上的割点。这时并不用设置特殊点,发现毁掉任意一个点,其他点都可以通过割点到其他点双。
然后按照乘法原理把每种的方案数算一算即可。
时间复杂度 O ( n ) O(n) O(n)。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1005;
int n,m,root,tot,num;
int dfn[N],low[N],cut[N],vis[N];
int t,first[N],v[N],nxt[N];
void add(int x,int y) {nxt[++t]=first[x],first[x]=t,v[t]=y;}
void Max(int &x,int y) {if(x<y)x=y;}
void Min(int &x,int y) {if(x>y)x=y;}
void Clear(){
n=t=tot=num=0;
memset(first,0,sizeof(first));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(vis,0,sizeof(vis));
memset(cut,0,sizeof(cut));
}
void Tarjan(int x,int son=0){
dfn[x]=low[x]=++tot;
for(int i=first[x];i;i=nxt[i]){
int to=v[i];
if(!dfn[to]){
son++,Tarjan(to),Min(low[x],low[to]);
cut[x]|=(root==x&&son>1)|(root!=x&&low[to]>=dfn[x]);
}
else Min(low[x],dfn[to]);
}
}
int cnt_cut,cnt_pnt,ans1;
ll ans2;
void dfs(int x,int id){
vis[x]=id,cnt_pnt++;
for(int i=first[x];i;i=nxt[i]){
int to=v[i];
if(cut[to]&&vis[to]!=id) vis[to]=id,cnt_cut++;
if(!vis[to]) dfs(to,id);
}
}
int main(){
int Case=0;
while((~scanf("%d",&m))&&m){
Clear();
for(int i=1,x,y;i<=m;++i){
scanf("%d%d",&x,&y);
add(x,y),add(y,x),Max(n,max(x,y));
}
for(int i=1;i<=n;++i)
if(!dfn[i]) Tarjan(root=i);
ans1=0,ans2=1;
for(int i=1;i<=n;++i){
if(cut[i]||vis[i]) continue;
cnt_cut=cnt_pnt=0,dfs(i,++num);
if(cnt_cut==0) ans1+=2,ans2*=cnt_pnt*(cnt_pnt-1)/2;
if(cnt_cut==1) ans1+=1,ans2*=cnt_pnt;
}
printf("Case %d: %d %lld\n",++Case,ans1,ans2);
}
return 0;
}