问题概述:给你一个无向联通图,你要在图中标记一些点,使得这个图中的任意一个点消失了,剩余的点都可以通
过一条路径到达你某个标记的点。问你最少需要选择多少个点,并且在最优的情况下有多少总选点方案,(每个样
例输入的第一个数m表示图中有多少条边,当m为0时读入结束)
输入样例: 对应输出:
9 Case 1: 2 4
1 3 4 1
3 5 1 2
2 6 1 5
6 3 1 6 3 2
UVALive - 5135
点双连通图:该无向图中的任意两个顶点间都存在两条点不相交的路径
(或是说该图中去掉任意一个点都不会影响其联通性)
定理:
①如果一个连通图不是点双联通图,那么必定存在至少一个点,如果这个点消失,图就会被分成多块,这样的点也
被称为割点
②点双联通分量一定是边双联通分量(除两点一线的特殊情况),反之不一定
③点双联通分量可以有公共点
Trajan主要原理:
time[k]:第一次遍历到k点的时间
low[k]:k点所在点双联通分量子图中第一个被搜到的点的time值
vis[k]:k点是否已经遍历
主要思路:一般来讲无论求解点双联通还是边双连通都是边入栈,如果点入栈,而因为性质③,你将一个点出栈
后,还可能有别的点双联通分量包含它,所以点入栈会导致一些错误和麻烦,但这里有种方法可以不需要栈,大概
思路是把所有的割点找出来(割点的time[]值一定会小于等于他搜索子树儿子点的low[]值,至于为什么?很显然如果一个点不是割点,那么它必定在一个环上或它自身是一个点双联通分量,考虑前者:那么这个点的深搜子树一定
会与他的某个父亲相连接,这样这个点儿子的low[]值就会是这个点某个父亲的low[]值,所以一定小于本身的time[]
值;考虑后者,它就不存在深搜子树更没有儿子,根本不会比较),然后再搜索一遍找出所有在同一点双联通分量
中的点即可。
注意:搜索对于根节点因为它没有父亲节点,所以无法通过上述方法判断根是否是割点,那怎么办?如果根有两个
搜索子树,那么它一定是个割点,反之不是
搜索过程:
→当点k有与点c相连,如果此时(time[k]时)c没有搜过,搜索c点,当c点及其子树搜索完毕回溯后,low[k] =
min(low[k], low[c]),如果此时low[c]的值>=time[k]的值,则说明点k是割点,否则不是
→当点k有与点c相连,如果此时(time[k]时)c已经搜过,low[k] = min(low[k], time[c])
期间保证:每个点每条边都只被搜索1次,且必须搜索1次,复杂度n+m,
此题题解:
看懂这题后第一眼想到是此图有多少个点双联通分量,那么就至少需要标记多少个点,然后情况数就是所有点双联
通分量中点个数的乘积,但没这么简单,要注意以下两点:
①:如果这个图本身是个点双联通分量,那么还是必须要标记两个点,因为有可能标记的点会被去掉,情况数就是
n*(n-1)/2,其中n为图中点的数量
②:如果一个点双联通分量中有不止一个割点,那么这个点双联通分量中就不需要标记任何一个点,因为其中一个
割点被去掉,还可以走另外一个割点到另一个双连通分量中
#include
#include
#include
#include
#include
using namespace std;
vector G[50005];
set st;
int n, t, sum, cutsum, vis[50005], low[50005], time[50005], cut[50005];
void Tarjan(int u, int p);
void Sech(int u);
int main(void)
{
int i, m, u, v, cas = 1;
long long bet, ans;
while(scanf("%d", &m), m!=0)
{
cutsum = 0;
t = n = 1;
memset(vis, 0, sizeof(vis));
memset(cut, 0, sizeof(cut));
for(i=1;i<=m+2;i++)
G[i].clear();
for(i=1;i<=m;i++)
{
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
n = max(n, max(v, u));
}
Tarjan(1, 0);
memset(vis, 0, sizeof(vis));
st.clear();
ans = 1, bet = 0;
for(i=1;i<=n;i++)
{
sum = 0;
if(vis[i]==0 && cut[i]==0)
{
st.clear();
Sech(i);
if(st.size()==1)
{
ans *= sum;
bet++;
}
}
}
if(cutsum==0)
printf("Case %d: 2 %lld\n", cas++, (long long)n*(n-1)/2);
else
printf("Case %d: %lld %lld\n", cas++, bet, ans);
}
return 0;
}
void Tarjan(int u, int p)
{
int i, v, child = 0;
vis[u] = 1;
low[u] = time[u] = t++;
for(i=0;i1 || p!=0 && low[v]>=time[u])
cut[u] = 1, cutsum++;
}
else
low[u] = min(low[u], time[v]);
}
}
void Sech(int u)
{
int i, v;
sum++;
vis[u] = 1;
for(i=0;i