bzoj 2730 [HNOI2012]礦場搭建
題面
Description
煤礦工地可以看成是由隧道連線挖煤點組成的無向圖。為安全起見,希望在工地發生事故時所有挖煤點的工人都能有一條出路逃到救援出口處。於是礦主決定在某些挖煤點設立救援出口,使得無論哪一個挖煤點坍塌之後,其他挖煤點的工人都有一條道路通向救援出口。請寫一個程式,用來計算至少需要設定幾個救援出口,以及不同最少救援出口的設定方案總數。
Input
輸入檔案有若干組資料,每組資料的第一行是一個正整數 N(N≤500),表示工地的隧道數,接下來的 N 行每行是用空格隔開的兩個整數 S 和 T,表示挖 S 與挖煤點 T 由隧道直接連線。輸入資料以 0 結尾。
Output
輸入檔案中有多少組資料,輸出檔案 output.txt 中就有多少行。每行對應一組輸入資料的 結果。其中第 i 行以 Case i: 開始(注意大小寫,Case 與 i 之間有空格,i 與:之間無空格,: 之後有空格),其後是用空格隔開的兩個正整數,第一個正整數表示對於第 i 組輸入資料至少需 要設定幾個救援出口,第二個正整數表示對於第 i 組輸入資料不同最少救援出口的設定方案總 數。輸入資料保證答案小於 2^64。輸出格式參照以下輸入輸出樣例。
題解
題目中沒有說tarjan圖是不是連通的,但是資料看來是保證連通的。為了保險我反正是寫了不保證連通的。那麼需要列舉每一個連通塊。
我們來分析一下題目。如果我們建在了割點的地方,那麼…一旦坍塌了你什麼都不是…而且我們還需要在割點割出來的幾個小連通塊各建一個逃生出口。建完之後發現割點的地方的逃生出口完全不必要…
題目中的意思是同時只會有一個地方坍塌,那麼這就好辦了,我們只需要在僅與一個割點相鄰的小連通塊的地方建立逃生出口就行了。(與兩個及兩個以上的割點相鄰的小連通塊不用建立)。
那麼題目中的兩問都可以解答了。
第一問:求僅與一個割點相鄰的小連通塊的數量。
第二問:僅與一個割點相鄰的小連通塊的規模乘積(乘法原理)。
特判:
如果一個連通塊他沒有割點,那麼一定需要兩個逃生出口(有可能逃生出口坍塌了)。
方案數就是:
具體做法
理論性的東西扯完了,我們來談談具體做法。
我們對於每一個連通塊,先做一遍tarjan,求出所有的割點。然後從每一個割點出發dfs,到下一個割點結束,進行染色,統計每一個點被染色的次數。
如果一個點只被染色一次,那麼它就是在我們要求的塊兒裡面了。再dfs就行了。
然後把答案壓入一個vector,再求乘積。
失誤
我在寫這道題目的時候,特判都想到了,但是沒有注意到要/2.。。。真是蠢得一批
code
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int num=0;char c=' ';bool flag=true;
for(;c>'9'||c<'0';c=getchar())
if(c=='-')
flag=false;
for(;c>='0'&&c<='9';num=(num<<3)+(num<<1)+c-48,c=getchar());
return flag ? num : -num;
}
namespace graph{
const int maxn=15200;
struct node{
int y,next;
}a[maxn<<1];
int head[maxn],top=0;
void insert ( int x, int y ) {
a[top].y = y;
a[top].next = head[x];
head[x] = top++;
}
bool flag=true;
int n,maxver=0;
void init() {
memset(head , -1, sizeof head );
n=read();
if(n==0){
flag=false;
return;
}
for (int i=1;i<=n;i++){
int x=read();
int y=read();
insert(x,y);
insert(y,x);
maxver=max(maxver,x);
maxver=max(maxver,y);
}
}
}using namespace graph;
namespace TARJAN{
int dfn[maxn],low[maxn],cnt=0,root=0;
bool cut[maxn];
void tarjan(int x){
dfn[x]=low[x]=++cnt;
int count=0;
for(int i=head[x];i+1;i=a[i].next){
int y=a[i].y;
if(!dfn[y]){
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]){
count++;
if(x!=root||count>1)
cut[x]=true;
}
}
else
low[x]=min(low[x],dfn[y]);
}
}
}using namespace TARJAN;
namespace ANSWER{
long long ans=0,ways=1;
int color[maxn];
bool vis[maxn];
void Ran(int x){
if(!cut[x])color[x]++;
vis[x]=true;
for(int i=head[x];i+1;i=a[i].next){
int y=a[i].y;
if(cut[y]||vis[y])continue;
Ran(y);
}
}
int count=0;
void Search(int x){
count++;
vis[x]=1;
for(int i=head[x];i+1;i=a[i].next){
int y=a[i].y;
if(vis[y]||cut[y])continue;
Search(y);
}
}
void work(){
vector<int>part;
while(part.size())part.pop_back();
for(int i=1;i<=maxver;i++)
if(cut[i]){
memset(vis,0,sizeof vis);
Ran(i);
}
memset(vis,0,sizeof vis);
for(int i=1;i<=maxver;i++)
if(color[i]==1&&!vis[i]&&!cut[i]){
count=0;
Search(i);
part.push_back(count);
}
ans+=(long long)part.size();
int t=1;
for(int i=0;i<part.size();i++){
ways*=(long long)part[i];
}
}
}using namespace ANSWER;
void Clear(){
memset(dfn,0,sizeof dfn);
memset(low,0,sizeof low);
memset(cut,0,sizeof cut);
memset(color,0,sizeof color);
memset(vis,0,sizeof vis);
cnt=0;
maxver=0;
ans=0;
ways=1;
}
void check_cuts(){
int num=0;
for(int i=1;i<=maxver;i++){
if(cut[i])num++;
}
printf("\nhave %d cuts\nThey are:",num);
for(int i=1;i<=maxver;i++){
if(cut[i])printf(" %d ",i);
}
cout<<endl;
}
bool Tepan(int now){
for(int i=1;i<=maxver;i++){
if(cut[i])return true;
}
ANSWER::count=0;
memset(vis,0,sizeof vis);
ans+=2;
ANSWER::Search(now);
ways*=ANSWER::count;
ways*=(ANSWER::count-1);
ways/=2;
return false;
}
int main(){
int _=0;
while(++_){
Clear();
init();
if(!flag)break;
for(int i=1;i<=maxver;i++)
if(!dfn[i]){
root=i;
memset(cut,0,sizeof cut);
tarjan(i);
//check_cuts();
if(Tepan(i))ANSWER::work();
}
printf("Case %d: %lld %lld\n",_,ans,ways);
}
return 0;
}