题目链接: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;
}