题目大意:求一张图上的割顶,并求出去掉这些割顶会产生多少个联通块。
思路:裸的求割顶的算法。
想过某个问题,如果同时有两条边连接同一个割顶,而这两条边又能通过别的边相连。去掉割顶之后只会多出一个联通块,而在给联通块计数的时候会不会把这两天条边都当做是可行的儿子记录下来。
答案是不会的。因为被访问过的点都被染色了。而搜索到被染色的点时,只会比较一下它的dep值和父亲的low值,而不会对这个节点进行进一步搜索。
注释有点多,其实是给我自己看的。防止以后忘了怎么做 了。。。智商堪忧
//下面文章引自:http://blog.sina.com.cn/s/blog_5b07e49c0100drwd.html
//下划线有改动
割顶:
连通图G的一个顶点子集V,如果删除这个顶点子集和它所附带的边后,图便不再连通。则称V是G的割顶集。
最小割顶集中顶点的个数,称为G的连通度。连通度等于1时,割顶集中的那个顶点叫做割顶。
注意:完全图的连通度为总顶点数-1;
牵一发而动全身的点称为割点
边连通度:
连通图G的一个边子集E,如果删除边子集的边后,图便不再连通,则称E是G的桥集。
含有最小边数的桥集的边数|E|称为G的边连通度。|E|=1时,E中的边叫做桥。
注意:规定不连通图的边连通度为0;完全图的边连通度为总顶点数-1;
连通图的两个特征:
1 连通度<=边连通度<=顶点数
2 顶点数大于2的2连通图的充分必要条件是任两个顶点在一个圈上.(没搞明白)
块的概念:
没有割点的连通子图,这个子图中的任何一对顶点之间至少存在两条不相交的路径,或者说要使两个站点同时发生故障至少两个站点同时发生故障,这种二连通分支称为块.
显然各个块之间的关系有如下两种:
1 互不连接
2 通过割顶连接(割顶可以属于不同的块,也可以两个块公有一个割顶)
引申:无向图寻找块,关键是找割顶.
满足是割顶的条件:
1 如果u不是根,u成为割顶的充要条件:u至少有一个儿子顶点s,从s或者s的后代点到u的祖先点之间不存在后向边.
2 如果u是根,则u成为割顶当且仅当它不止有一个儿子点.
怎样求割顶:
引入一个标号函数:
low(u)=min{dep(u),low(s),dep(w)}; s是u的一个儿子,(u,w)是后向边
显然low(u)值是u或者u的后代所能追溯到的最早(序号小)的祖先序号.
利用标号函数low,分析求割顶的步骤:
顶点u不为根且为割顶的条件是u至少有一个儿子s,使得low(s)>=dep(u),即s和s的后代不会追溯到比u更早的祖先点.
顶点u为根且为割顶的条件是不止有一个儿子s,使得low(s)>=dep(u)。
low(u)的计算步骤:
1 low(u)=dep(u);
//u在dfs过程中首次被访问
2 low(u)=min{low(u),dep(w)}
//检查后向边(u,w)时
3 low(u)=min{low(u),low(s)}
//u的儿子s的关联边被检查时
// poj 1523 SPF
//wa了好久,因为把dep当成是搜索的层数,所以导致每个点的标号总是不断改变
//每个点的low值也不断改变,所以老错。
//正确算法是每个点按照搜索的顺序标号,这是dep的真正含义。
//感觉不会再爱了。。。
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
int low[1100],dep[1100];
int SPFNumber[1100];//求割顶被求次数,用来求去掉割顶后分成多少联通块
int boo[1100];//染色标记
int deep;//给图上所有点按照遍历顺序标号,每个点的标号不重记
int bridge;//求桥的数量
int tip;
//邻接表
int now[1100],test=0;
int tail;
class PPP
{
public:
int point;
int next;
}way[100000];
void clearlist()
{
tail = 0;
memset(now,0,sizeof(now));
memset(SPFNumber,-1,sizeof(SPFNumber));
memset(boo,0,sizeof(boo));
}
void insert(int a,int b)
{
tail++;
way[tail].point = b;
way[tail].next = now[a];
now[a] = tail;
}
//基于深搜的标记法求割顶与桥
void dfs(int k,int father)
{
int son;
//发现一个没有标记的新点
boo[k] = 1;
dep[k] = ++deep;
low[k] = dep[k];//初始化low值为当前点的dep值
for (int i = now[k] ; i != 0 ; i = way[i].next)
{
son = way[i].point;
//让点不能访问父亲节点,用于求桥。否则所以点的low值都会等于父亲节点的dep
if (son == father) continue;
//如果探索到的点已经被标记,则它可能是较早的祖先
if (boo[son]) {
low[k] = low[k]<dep[son]?low[k]:dep[son];
continue;
}
//如果探索到的点没有被标记,则它一定是一个新点
dfs(son,k);
//用儿子点的low值更新自己的low值,儿子点能碰到的最早祖先,自己也能碰到
low[k] = low[k]<low[son]?low[k]:low[son];
//判断割顶
if (low[son] >= dep[k]) SPFNumber[k] ++;
//判断桥
if (low[i] > dep[k]) bridge ++;
}
}
int main()
{
int x,y;
while (1)
{
test++;
deep = 0;
clearlist();
scanf("%d",&x);
if (x == 0) break;
while (x != 0)
{
scanf("%d",&y);
insert(x,y);
insert(y,x);
SPFNumber[x] = 0;
SPFNumber[y] = 0;
scanf("%d",&x);
}
bridge = 0;
//找到第一个存在的节点作为搜索树的跟节点
x = 1;
while (SPFNumber[x] == -1) x++;
dfs(x,0);
//奇怪的输出。。。
tip = 0;
printf("Network #%d\n",test);
if (SPFNumber[x] > 1) {
printf(" SPF node %d leaves %d subnets\n",x,SPFNumber[x]);
tip = 1;
}
x++;
while (x < 1010)
{
while ((SPFNumber[x] <= 0)&&(x < 1010)) x++;
if (x < 1000) {
printf(" SPF node %d leaves %d subnets\n",x,SPFNumber[x]+1);
tip = 1;
}
x++;
}
if (tip == 0) printf(" No SPF nodes\n");
printf("\n");
}
return 0;
}
注意:对任何顶点u计算low(u)的值是不断修改的,只有当以U为根的dfs子树和后代的low值,dep值全部出现以后才停止.