题目描述
给出一张无向图,要求从这些点中选出若干救援点使得任意一个点删除后任意一个点都还能到达至少一个救援点,求最少需要设置几个救援点和设置最少救援点的方案数。
思路
显然这与点双联通图有关,我们先考虑对于一个点双联通图,如果要选救援点,我们只需要选任意的两个点即可(防止删除的点为救援点)。而对于非点双联通图,我们可以把它分割为若干点双联通图来统计答案,对于一个点双联通分量,如果它与两个及以上的割点相连,那么就无所谓,因为断掉之后仍然可以和另一个点双联通分量相连,我们只要考虑只与一个割点相连的点,那么我们必须要在这个点双联通分量中选出一个点。因此,就有三种情况(设与之相连的割点数为sum):
①若sum==0,需要建两个节点,方案数为siz*(siz-1)/2
②若sum==1,需要建一个节点,方案数为siz-1(不能建在割点上)
③若sum>1,不用建节点。
最后把方案数用乘法原理累乘起来即可。
而对于点双联通分量的求法,我们可以用tarjan,对于每个dfn[u]≤low[v],意味着不可以从v回到u的祖先节点,那么u就是割点,在u以下的搜索子树的节点形成一个点双连通分量,我们直接建图即可,注意每个割点都属于它连接的几个点双连通分量。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N=550,M=N<<1; struct Edge { int to,nxt; }e[M]; int nxt[M],to[M],tot,head[N]; void add_edge(int x,int y) { nxt[++tot]=head[x]; head[x]=tot; to[tot]=y; } int poi[N],tt; void add(int x,int y) { e[++tt].nxt=poi[x]; poi[x]=tt; e[tt].to=y; } int read() { int res=0,w=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)w=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){res=(res<<3)+(res<<1)+ch-‘0‘;ch=getchar();} return res*w; } int dfn[N],low[N],st[N],siz[N],root,col,top,idx; bool cut[N]; void tarjan(int u) { dfn[u]=low[u]=++idx; st[++top]=u; int cnt=0; for(int i=head[u];i;i=nxt[i]) { int v=to[i]; if(!dfn[v]) { cnt++; tarjan(v); low[u]=min(low[v],low[u]); if((u==root&&cnt>1)||(u!=root&&dfn[u]<=low[v])) cut[u]=1; if(low[v]>=dfn[u]) { ++col; while(st[top]!=v) { add(col,st[top]); siz[col]++; --top; } --top; siz[col]+=2; add(col,v); add(col,u); } } else low[u]=min(low[u],dfn[v]); } } void clear() { memset(head,0,sizeof(head)); memset(poi,0,sizeof(poi)); memset(siz,0,sizeof(siz)); memset(dfn,0,sizeof(dfn)); memset(cut,0,sizeof(cut)); tot=0;tt=0;col=0;top=0;idx=0; } int main() { int cas=0,n,m; while(m=read()) { if(m==0)break ; clear(); n=0; for(int i=1;i<=m;i++) { int s=read(),t=read(); add_edge(s,t);add_edge(t,s); n=max(n,max(s,t)); } for(int i=1;i<=n;i++) if(!dfn[i]) { root=i; tarjan(i); } ll ans=1,res=0; for(int i=1;i<=col;i++) { int sum=0; for(int j=poi[i];j;j=e[j].nxt) if(cut[e[j].to])sum++; if(sum==0)res+=2,ans=ans*(siz[i])*(siz[i]-1)/2; else if(sum==1)res++,ans=ans*(siz[i]-1); } printf("Case %d: %lld %lld\n",++cas,res,ans); } }
原文:https://www.cnblogs.com/fangbozhen/p/11740829.html