题面
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图是不是连通的,但是数据看来是保证连通的。为了保险我反正是写了不保证连通的。那么需要枚举每一个连通块。
我们来分析一下题目。如果我们建在了割点的地方,那么…一旦坍塌了你什么都不是…而且我们还需要在割点割出来的几个小连通块各建一个逃生出口。建完之后发现割点的地方的逃生出口完全不必要…
题目中的意思是同时只会有一个地方坍塌,那么这就好办了,我们只需要在仅与一个割点相邻的小连通块的地方建立逃生出口就行了。(与两个及两个以上的割点相邻的小连通块不用建立)。
那么题目中的两问都可以解答了。
第一问:求仅与一个割点相邻的小连通块的数量。
第二问:仅与一个割点相邻的小连通块的规模乘积(乘法原理)。
特判:
如果一个连通块他没有割点,那么一定需要两个逃生出口(有可能逃生出口坍塌了)。
方案数就是:
连通块规模∗(连通块规模−1)2
连
通
块
规
模
∗
(
连
通
块
规
模
−
1
)
2
具体做法
理论性的东西扯完了,我们来谈谈具体做法。
我们对于每一个连通块,先做一遍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;
}