题目大意:给定一个无向图,要求将一些点设为出口 要求图中删掉任意一个点后剩余的任意一个点都与至少一个出口相连 求最少建多少个出口以及建最少出口的方案数
首先看到割点就是Tarjan搞 但是怎么搞
首先假设我们把所有的点双都缩点 那么我们一定可以得到一棵树 然后我们就会发现
叶子节点(只含有一个割点的点双)必须建 因为叶子节点如果不建 一旦割点被爆就死翘了
非叶节点(含有两个或两个以上的割点的点双)不用建 因为即使一个割点被爆了也可以沿着另一个割点走到一个叶节点
还有一种情况就是整个联通块都是点双(即不含割点的点双) 这样我们讨论点双的大小
如果只有一个点 那么这个点必须建 数据没有卡这个的点所以我没写(其实是我忘写了 然后还过了)
如果有两个或两个以上的点 那么要建两个 一个被爆了还可以走另一个
方案数就是乘法原理的问题了 注意叶节点那里出口不能建在割点上
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define M 1010 using namespace std; typedef long long ll; struct abcd{ int to,next; }table[M]; int head[M],tot=1; int n,m,cases,ans1; long long ans2; int dpt[M],low[M],cnt,belong[M],stack[M],top; inline void Initialize() { memset(head,0,sizeof head); memset(dpt,0,sizeof dpt); memset(belong,0,sizeof belong); tot=1;n=0;ans1=0;ans2=1; } void Add(int x,int y) { table[++tot].to=y; table[tot].next=head[x]; head[x]=tot; } void Tarjan(int x) { int i; dpt[x]=low[x]=++cnt; for(i=head[x];i;i=table[i].next) { int y=table[i].to; if(dpt[y]) low[x]=min(low[x],dpt[y]); else { Tarjan(y); low[x]=min(low[x],low[y]); if(low[y]>=dpt[x]) belong[x]++; } } } void _Tarjan(int x) { int i; dpt[x]=low[x]=++cnt; stack[++top]=x; for(i=head[x];i;i=table[i].next) { int y=table[i].to; if(dpt[y]) low[x]=min(low[x],dpt[y]); else { _Tarjan(y); low[x]=min(low[x],low[y]); if(low[y]>=dpt[x]) { int t,temp=0,size=0; do{ t=stack[top--]; if(belong[t]>=2) ++temp; ++size; }while(t!=x); stack[++top]=x; if(!temp) ans1+=2,ans2*=size*(size-1)/2; else if(temp==1) ans1++,ans2*=size-1; } } } } int main() { //freopen("2730.in","r",stdin); int i,x,y; while(scanf("%d",&m),m) { Initialize(); for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); n=max(n,x); n=max(n,y); Add(x,y); Add(y,x); } for(i=1;i<=n;i++) if(!dpt[i]) Tarjan(i); else belong[i]++; memset(dpt,0,sizeof dpt); for(i=1;i<=n;i++) if(!dpt[i]) _Tarjan(i); cout<<"Case "<<++cases<<": "; cout<<ans1<<' '<<ans2<<endl; } }
时间: 2024-10-28 23:39:01