題目鏈接:https://www.luogu.com.cn/problem/P3225
解決這道題目之前,先說明兩個知識點。
什麼是點雙?點雙即點雙連通分量,在一個連通圖裏,任意點到其他點都有兩條點不重複路徑,這就被稱爲點雙連通分量,僅有一條邊爲特殊的點雙。
什麼是割點?割點就是在一個連通圖裏,刪去這個點後,連通分支增多的那個點。可以用tarjan來找。
不同點雙至多隻有一個公共點,並且是割點,每個割點至少是兩個點雙的公共點。
說完知識點,再看這題,本題可以分成三種情況。
情況一
點雙沒有割點,要建兩個出口,一個出口塌了還可以跑另一個出口,方案數有 C2n(n爲該點雙裏的點數,除割點外) 種,即n(n-1)/2。
情況二
點雙有一個割點(紅色爲割點)(圖是上下兩個點雙),要建一個出口,因爲這個出口塌了,可以通過割點跑到另一個點雙的那個出口。方案數有 n 種。
情況三
點雙有至少兩個割點(紅色爲割點),圖中爲上中下三個點雙,不用建出口,因爲一個割點塌了,還可以通過另一個割點跑到其它點雙的出口。
我們用tarjan找完割點之後,搜索每一個點雙,統計它所含的割點數目,除割點外其它點的數目,更新答案即可,另外多組數據,別忘記初始化。
代碼如下
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=505;
int head[maxn],dfn[maxn],low[maxn];
int col[maxn];//每個點的顏色
bool cut[maxn];//標記是否爲割點
int top,cnt,deg;
int ans1;//答案一
int dfn_num;
int root;//根
int s;//一個連通塊除割點外的數量
int sum;//一個連通塊割點的數量
int col_id;//顏色編號
ll ans2;//答案二
int n,m;
struct node
{
int to,next;
}edge[maxn*2];//無向邊
void add(int x,int y)//加邊
{
edge[++cnt].next=head[x];
edge[cnt].to=y;
head[x]=cnt;
}
void init()//初始化
{
top=cnt=deg=ans1=dfn_num=0,n=0,col_id=0;
ans2=1;
memset(cut,0,sizeof(cut));
memset(head,0,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(col,0,sizeof(col));
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++dfn_num;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(!dfn[v])
{
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
if(u==root)
deg++;
else
cut[u]=1;
}
}
else if(v!=fa)
low[u]=min(low[u],dfn[v]);
}
}
void dfs(int u)//遍歷每個連通塊
{
col[u]=col_id;//染色
if(cut[u])
return ;
s++;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(cut[v]&&col[v]!=col_id)
{
sum++;//割點數目+1
col[v]=col_id;
//將該割點染爲該連通塊的顏色
}
if(!col[v])
dfs(v);
}
}
int main()
{
int t=0;
while(~scanf("%d",&m)&&m)
{
init();
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
n=max(n,max(x,y));
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)
{
if(!dfn[i])
{
root=i;
tarjan(i,0);
}
if(deg>=2)//根節點有兩個孩子就是割點
cut[root]=1;
deg=0;
}
//puts("1");
for(int i=1;i<=n;i++)
{
if(!col[i]&&!cut[i])//不是割點且未被染色
{
col_id++,s=sum=0;
dfs(i);
if(!sum)//沒割點
ans1+=2,ans2*=s*(s-1)/2;//建兩個
if(sum==1)//一個割點
ans1++,ans2*=s;//建一個
//兩個割點,不用建
}
}
printf("Case %d: %d %lld\n",++t,ans1,ans2);
}
return 0;
}