【tarjan求割顶】BZOJ2730-[HNOI2012]矿场搭建
【题目大意】
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
【思路】
可以得出这样的结论:
(1)如果一个点是割点,那么在它上面建救援出口是没有意义的
(2)对于出去割点后的连通块,如果它和两个及以上的割点相连,这不需要建救援出口;否则要建一个救援出口,在连通块里选任意一个即可,最终方案数为这些连通块大小的乘积。
(3)如果只有一个联通块,那么至少建两个救援出口,最终方案书为(size)*(size-1)/2。
所以这样做:
首先用tarjan求出割点并记录,然后跑dfs求出除去割点后的连通块的个数和大小,再依据上述结论输出答案。
【错误点】
数组从0开始和从1开始搞错了一次..判断连通块周围割点的个数的时候弄错了…具体见注释
一开始忘记清空vector,后来发现用clear清空的话会RE。请教之后才知道vector清空的正确方式长成这个样子:vector<int>().swap(E[i])
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 const int MAXN=10000+50; 8 vector<int> E[MAXN]; 9 int kase=0; 10 int n,m;//隧道的数量和矿井的数量 11 int dfn[MAXN],low[MAXN],vis[MAXN]; 12 int cut[MAXN],cnt;//记录每一个点是否是割点以及连通块的总数 13 int nearcut[MAXN],size[MAXN];//当前连通块可以到达的割点数量&每个连通块的大小 14 int t; 15 int viscut[MAXN]; 16 17 int tarjan(int isrt,int u) 18 { 19 vis[u]=1; 20 dfn[u]=low[u]=++t; 21 int sonnum=0; 22 for (int i=0;i<E[u].size();i++) 23 { 24 int son=E[u][i]; 25 if (!vis[son]) 26 { 27 sonnum++; 28 tarjan(0,son); 29 low[u]=min(low[u],low[son]); 30 if (isrt && sonnum>=2) cut[u]=1;//如果是根节点,且孩子的数量大于等于两个,则是割点 31 if (!isrt && dfn[u]<=low[son]) cut[u]=1; //如果不是根节点,并且孩子的low大于等于当前的dfn,即没有走到过u之前,则是割点 32 } 33 else 34 low[u]=min(low[u],dfn[son]); 35 } 36 } 37 38 void dfs(int u) 39 { 40 vis[u]=1; 41 size[cnt]++; 42 for (int i=0;i<E[u].size();i++) 43 { 44 int son=E[u][i]; 45 if (!vis[son]) 46 if (cut[son]) 47 { 48 if (viscut[son]!=cnt) 49 { 50 //错误点:我一开始直接cnt++了,但是这样是不对的,会导致同一个割点被多次累加 51 nearcut[cnt]++; 52 viscut[son]=cnt; 53 } 54 } 55 else dfs(son); 56 } 57 } 58 59 void init() 60 { 61 for (int i=0;i<MAXN;i++) vector<int>().swap(E[i]); 62 m=0; 63 for (int i=0;i<n;i++) 64 { 65 int u,v; 66 scanf("%d%d",&u,&v); 67 E[u].push_back(v); 68 E[v].push_back(u); 69 m=max(m,max(u,v));//记下总共的矿井数量 70 } 71 } 72 73 void solve() 74 { 75 memset(vis,0,sizeof(vis)); 76 memset(cut,0,sizeof(cut)); 77 memset(size,0,sizeof(size)); 78 memset(viscut,0,sizeof(viscut)); 79 t=0; 80 for (int i=1;i<=m;i++)//错误点:数组都要从1开始 81 { 82 if (vis[i]==0) 83 tarjan(1,i);//依次找出所有的割点 84 } 85 memset(vis,0,sizeof(vis)); 86 cnt=0; 87 for (int i=1;i<=m;i++) 88 if (!cut[i] && !vis[i]) 89 { 90 cnt++; 91 nearcut[cnt]=0; 92 dfs(i); 93 } 94 } 95 96 97 void output() 98 { 99 printf("Case %d: ",kase); 100 if (cnt==1) 101 { 102 printf("2 %lld\n",(long long)m*(m-1)/2); 103 return; 104 } 105 106 long long tot=0; 107 long long ans=1; 108 if (cnt>1) 109 { 110 for (int i=1;i<=cnt;i++) 111 { 112 if (nearcut[i]==1) 113 { 114 tot++; 115 ans*=size[i]; 116 } 117 } 118 printf("%lld %lld\n",tot,ans); 119 } 120 } 121 122 int main() 123 { 124 for(;;) 125 { 126 scanf("%d",&n); 127 if (n==0) break; 128 kase++; 129 init(); 130 solve(); 131 output(); 132 } 133 return 0; 134 }