BZOJ2730 矿场搭建
题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2730
基本思路,注意到n非常的小,所以暴力即可。
首先显然我们要找出所有的割点,$O(n^2)$并查集。
我们将所有的割点删除,然后将剩余的边用并查集维护,化为很多个联通块。
有三种情况:
1.联通块和一个割点相连,这种情况下显然只能在当前联通块里任选一个点建一个逃生点。
2.与两个割点相连,这种情况下不用建逃脱点因为他只能割掉其中一个割点。
3.没有割点,至少建两个(任选两个点),因为有可能逃生点被破坏了,所以为两个。
至于情况数,因为同一联通块的点没有不同,所以直接乘起来。
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <set> #define N 510 #define p E[i].x #define LL long long using namespace std; struct edge{ int x,to; }E[N<<1]; int m,n,g[N],totE,fa[N],cnt[N],siz[N]; set<int> G[N]; bool cut[N]; inline void addedge(int x,int y){ E[++totE]=(edge){y,g[x]}; g[x]=totE; E[++totE]=(edge){x,g[y]}; g[y]=totE; } int find(int x){ if(fa[x]!=x) fa[x]=find(fa[x]); return fa[x]; } void add(int x,int y){ int r1=find(x),r2=find(y); if(r1!=r2) fa[r2]=r1; } bool check(int x){ for(int i=1;i<=n;i++) fa[i]=i; for(int i=2;i<=totE;i+=2) if(E[i].x!=x&&E[i^1].x!=x) add(E[i].x,E[i^1].x); int tmp=0; for(int i=1;i<=n;i++){ if(i==x) continue; if(!tmp) tmp=find(i); if(find(i)!=tmp) return 1; } return 0; } int main(){ int caset=0; while(scanf("%d",&m)==1){ if(!m) break; for(int i=1;i<=n;i++){ cut[i]=0; siz[i]=g[i]=cnt[i]=0; G[i].clear(); } printf("Case %d: ",++caset); totE=1; n=0; for(int i=1,x,y;i<=m;i++){ scanf("%d%d",&x,&y); addedge(x,y); n=max(n,x); n=max(n,y); } bool fl=0; for(int i=1;i<=n;i++) if(check(i)) cut[i]=1,fl=1; if(!fl){ printf("%d %lld\n",2,((n-1)*(LL)n)/2); continue; } for(int i=1;i<=n;i++) fa[i]=i; for(int i=2;i<=totE;i+=2) if(!cut[E[i].x]&&!cut[E[i^1].x]) add(E[i].x,E[i^1].x); for(int x=1;x<=n;x++){ siz[find(x)]++; if(!cut[x]) continue; for(int i=g[x];i;i=E[i].to) if(!cut[p]) G[find(p)].insert(x); } int ans=0; LL ansv=1; for(int i=1;i<=n;i++){ if(cut[i]) continue; if(find(i)!=i) continue; if(G[i].size()<2){ ans++; ansv*=siz[i]; } } printf("%d %lld\n",ans,ansv); } }