【题解】洛谷 P3225 【[HNOI2012]矿场搭建】—— TarJan
传送门
前置知识:
这是一道好题 ,包含的知识有——(TarJan求割点,TarJan求点双联通分量,组合数学)
如果你不知道怎么用TarJan求割点请戳这里!
如果你不知道什么是点双联通分量请戳这里!
如果你不知道什么是组合数学? %一下PHenning你就会啦!%%%stO PHenning Orz%%%
题目描述
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入输出格式
输入格式:
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N<=500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。
输出格式:
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 264。输出格式参照以下输入输出样例。
输入样例 | 输出样例 |
---|---|
9 1 3 4 1 3 5 1 2 2 6 1 5 6 3 1 6 3 2 6 1 2 1 3 2 4 2 5 3 6 3 7 0 | Case 1: 2 4 Case 2: 4 1 |
说明
Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。
让我们来观察一下 这一组 的样例(原谅我的灵魂作图 ):
(红色表示割点,黄色的圈表示一个点双联通分量)
1.对于{4.7.8}这一个点双联通分量,我们很容易发现其中的割点数为0,那么很显然我们最少需要2个救援出口来确保百分百的安全(防止我们设为救援出口的那个点坍塌),那么方案数就是
C
(
2
3
)
C{2\choose 3}
C(32)
2.对于{1.2.3.5.6}这一个点双联通分量,我们很容易发现其中的割点数为1,那么我们最少需要1个救援出口来确保百分百的安全
- 1.[割点坍塌] 我们只需在剩下一条链中人选一点即可,方案数即为剩余点是数量
- 2[非割点坍塌] 由于剩下的图是联通的,所以剩余的点都可以从割点到达其它联通块,此情况不需要建立救援出口
但是为了以防万一(割点坍塌) 我们还是要建立一个救援出口,方案数为 C ( 1 非 割 点 数 ) C{1\choose 非割点数} C(非割点数1)
3.还有一种情况:割点数>=2 这种情况最好处理,因为你只会坍塌一个点,但是在割点数>=2的这种条件下,无论你坍塌哪个点我都可以从其它的割点处到达下一个点双连通分量,所以此情况对答案无贡献
总结一下
对于点双连通分量K:
{
i
f
(
割
点
数
=
=
0
)
−
a
n
s
+
=
2
,
S
o
l
u
t
i
o
n
∗
=
C
(
2
总
点
数
)
i
f
(
割
点
数
=
=
1
)
−
a
n
s
+
+
,
S
o
l
u
t
i
o
n
∗
=
C
(
1
非
割
点
数
)
i
f
(
割
点
数
>
=
2
)
c
o
n
t
i
n
u
e
\begin{cases} if(割点数==0)-ans+=2,Solution*=C{2\choose 总点数}\\if(割点数==1)- ans++,Solution*=C{1\choose 非割点数}\\ if(割点数>=2)continue\end{cases}
⎩⎪⎨⎪⎧if(割点数==0)−ans+=2,Solution∗=C(总点数2)if(割点数==1)−ans++,Solution∗=C(非割点数1)if(割点数>=2)continue
下面是代码实现:
#include<stdio.h>
#include<bits/stdc++.h>
#define H 2005
#define ll long long
#define MT(a,b) memset(a,b,sizeof(a))
using namespace std;
int N,P,Time;
int LA[H],ED[H],NT[H],tot;
int dfn[H],low[H],Vistime;
int Vis[H],scc;
ll Cut,Point,Solution,Ans;
bool CutPoint[H];
void LB(int u,int v){
ED[++tot]=v;NT[tot]=LA[u];LA[u]=tot;
ED[++tot]=u;NT[tot]=LA[v];LA[v]=tot;
}
void TarJan_CutPoint(int u,int fa){
dfn[u]=low[u]=++Vistime;
int Son=0;
for(int i=LA[u];i;i=NT[i]){
int v=ED[i];
if(!dfn[v]){
TarJan_CutPoint(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=fa)CutPoint[u]=1;
if(u==fa)Son++;
}
low[u]=min(low[u],dfn[v]);
}
if(Son>=2&&u==fa)CutPoint[u]=1;
}
void TarJan_scc(int u){
Vis[u]=scc;
Point++;
for(int i=LA[u];i;i=NT[i]){
int v=ED[i];
if(Vis[v]!=scc&&CutPoint[v]){
Cut++;
Vis[v]=scc;
}
else if(!Vis[v]&&!CutPoint[v])TarJan_scc(v);
}
}
int main(){
while(1){
Time++;
tot=P=Vistime=Ans=scc=0;
Solution=1;
MT(LA,0);MT(dfn,0);MT(Vis,0);MT(CutPoint,0);
scanf("%d",&N);if(!N)return 0;
for(int i=1;i<=N;i++){
int u,v;scanf("%d%d",&u,&v);
LB(u,v);
P=max(u,max(v,P));
}
for(int i=1;i<=P;i++)if(!dfn[i])TarJan_CutPoint(i,i);//求割点
for(int i=1;i<=P;i++){
if(!Vis[i]&&!CutPoint[i]){
scc++;Cut=Point=0;
TarJan_scc(i);
if(Cut==0){
Ans+=2;
Solution*=Point*(Point-1)/2;
}
if(Cut==1){
Ans++;
Solution*=Point;
}
if(Cut>=2){
continue;
}
}
}
printf("Case %d: %lld %lld\n",Time,Ans,Solution);
}
}