点连通分量的例题
贴一下点连通分量的模板
int dfs_c,bcc_cnt;
int bccno[SIZE_D],iscut[SIZE_D],pre[SIZE_D];
struct Edge{int v,u;};
stack<Edge> sta;
vector<int> bcc[SIZE_D];
int untarjan(int ver,int fa)//点连通分量
{
int lowver = pre[ver] = ++dfs_c;//父节点的时间戳赋值为dfs——c,pre代表当前节点的时间戳。不改变
int child = 0;
for (int i = head[ver]; i != -1; i = pra[i].next){
int u = pra[i].to;
Edge temp = (Edge){ver,u};//把父节点和子节点存成边放进去存起来
if (!pre[u]){//如果子节点没有被访问过
sta.push(temp);//把边放进去栈
child++;//子节点个数加一
int lowu = untarjan(u,ver);//dfs。这样就能存进去新的点
lowver = min(lowu,lowver);//父节点的时间戳存成最早的父子节点那个时间戳
if (lowu >= pre[ver]){//如果子节点的时间戳在???
iscut[ver] = 1;
bcc_cnt++;//表示连通分量的个数
printf("ver%d, u%d, fa%d, bcc_cnt%d, lowu%d, lowver%d\n",ver, u, fa, bcc_cnt, lowu, lowver);
bcc[bcc_cnt].clear();//清空vector
while (1){
Edge x = sta.top(); sta.pop();
if (bccno[x.v] != bcc_cnt){//如果这个节点所属的连通分量 不是当前联通分量,放进去
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (bccno[x.u] != bcc_cnt){
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
}
printf("%d %d\n",x.u,x.v);
if (x.u == u && x.v == ver)//如果栈顶元素就是当前边
break;
}
}
}else if (pre[u]< pre[ver] && u != fa){//如果子节点的??? 小于父亲节点。而且子节点不是当前节点的父亲
sta.push(temp);
lowver = min (lowver, pre[u]);
}
}
if (fa < 0 && child == 1)
iscut[ver] = 0;//根据名字 我觉得这个数组的意思是 他是不是一个割点
return lowver;
}
解释一下这个模板。
把父节点和子节点都存一个时间戳。然后不断dfs,同时更新lowver,即父节点 最早的时间戳
如果更新成功,那就把栈里的边都取出来,放进同一个连通分量里面
如果子节点已经被访问过了,说明形成了一个环,更新lowver,即父节点 最早的时间戳,并且返回该时间戳的值
同时里面加上一个判断子节点个数的child变量。如果一个节点的父亲是-1而且只有一个孩子,那么这个根节点并不是割点
poj2942
题意就是:有些骑士互相憎恨。这些骑士不能挨着坐,现在有很多场圆桌会议,一场都不能参加的骑士会被赶走。问赶走多少个骑士
题解:给不互相憎恨的骑士之间连一条边,然后,每个骑士左右坐的人可以就是连线的两个人,所以不能存在叶子节点(也就是只有一条边和该节点相连).找出所有的圈。那么就需要存在点双连通分量才可以有圈。
而且根据题意。必须是奇圈(包含奇数个节点的圈)
注意:含有奇圈的双连通分量不一定是奇圈。因为双连通分量吧。可以有好多个圈,这些圈可以共用顶点。所以题意不明,造成了discuss里面的争论。根据ac的做法,每个骑士是可以同时参加一个以上的圆桌会议的
判断奇圈只能用二分图染色的方法。如果是一个奇圈,那么一定不存在二分图。这是充分必要条件,至于怎么证的,我不懂
然后含有奇圈的双连通分量里的所有点都可以标记了。只用把没标记的点驱逐就可以了
《算法竞赛入门经典训练指南?》上面的解法是找到每个字节点所属的双连通分量,判断是不是当前的双连通分量,然后染色,可是要是这个点刚好属于那个不被标记的双连通分量怎么办啊。不明白诶!我是直接找到双连通分量里的点,然后把该节点的子节点染色的。
/*
在不相互憎恨的骑士之间连一条边,这样如果有一个圈,那么这个骑士的两边都有人坐了
如果是一个奇圈,那么一定不存在二分图。充分不必要条件
根据网上流传的做法,题目是允许一个骑士坐在两个圆桌上的
*/
#include<iostream>
#include<stack>
#include<vector>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define SIZE_D 2005
#define SIZE_B 5000005
using namespace std;
int ishate[SIZE_D][SIZE_D];
int e,head[SIZE_D];
void init()
{
e = 0;
memset(head,-1,sizeof(head));
memset(ishate,0,sizeof(ishate));
}
struct pp
{
int to,next;
}pra[SIZE_B];
void addedge2(int x, int y)
{
pra[e].to = y;
pra[e].next = head[x];
head[x] = e++;
pra[e].to = x;
pra[e].next = head[y];
head[y] = e++;
}
int dfs_c,bcc_cnt;
int bccno[SIZE_D],iscut[SIZE_D],pre[SIZE_D];
struct Edge{int v,u;};
stack<Edge> sta;
vector<int> bcc[SIZE_D];
int untarjan(int ver,int fa)//点连通分量
{
int lowver = pre[ver] = ++dfs_c;//父节点的时间戳赋值为dfs——c,pre代表当前节点的时间戳。不改变
int child = 0;
for (int i = head[ver]; i != -1; i = pra[i].next){
int u = pra[i].to;
Edge temp = (Edge){ver,u};//把父节点和子节点存成边放进去存起来
if (!pre[u]){//如果子节点没有被访问过
sta.push(temp);//把边放进去栈
child++;//子节点个数加一
int lowu = untarjan(u,ver);//dfs。这样就能存进去新的点
lowver = min(lowu,lowver);//父节点的时间戳存成最早的父子节点那个时间戳
if (lowu >= pre[ver]){//如果子节点的时间戳在???
iscut[ver] = 1;
bcc_cnt++;//表示连通分量的个数
printf("ver%d, u%d, fa%d, bcc_cnt%d, lowu%d, lowver%d\n",ver, u, fa, bcc_cnt, lowu, lowver);
bcc[bcc_cnt].clear();//清空vector
while (1){
Edge x = sta.top(); sta.pop();
if (bccno[x.v] != bcc_cnt){//如果这个节点所属的连通分量 不是当前联通分量,放进去
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (bccno[x.u] != bcc_cnt){
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
}
printf("%d %d\n",x.u,x.v);
if (x.u == u && x.v == ver)//如果栈顶元素就是当前边
break;
}
}
}else if (pre[u]< pre[ver] && u != fa){//如果子节点的??? 小于父亲节点。而且子节点不是当前节点的父亲
sta.push(temp);
lowver = min (lowver, pre[u]);
}
}
if (fa < 0 && child == 1)
iscut[ver] = 0;//根据名字 我觉得这个数组的意思是 他是不是一个割点
return lowver;
}
/*
把父节点和子节点都寸一个时间戳。然后不断dfs,同时更新lowver,即父节点 最早的时间戳
如果更新成功,那就把栈里的边都取出来,放进同一个连通分量里面
如果子节点已经被访问过了,说明形成了一个环,更新lowver,即父节点 最早的时间戳,并且返回该时间戳的值
同时里面加上一个判断子节点个数的child变量。如果一个节点的父亲是-1而且只有一个孩子,那么这个根节点并不是割点
*/
void find_bcc(int n)
{
memset(pre,0,sizeof(pre));
memset(iscut, 0, sizeof(iscut));
memset(bccno, 0, sizeof(bccno));
dfs_c = bcc_cnt = 0;
for (int i = 1; i <= n; i++)
if (!pre[i])
untarjan(i,-1);
}
int color[SIZE_D];
int odd[SIZE_D];
int bi(int ver,int b,int x)
{
color[ver] = x;
for (int j = 0; j < bcc[b].size(); j++){
int u = bcc[b][j];
if (u != ver && ishate[ver][u] == 0){
if (color[u] < 0){
if (!bi(u,b,x^1) )
return 0;
}else{
if (color[u]!= x^1)
return 0;
}
}
}
return 1;
}
int bi2(int ver,int b,int x)//书上的算法并没有考虑一个点属于两个不同的联通分量的情况
{
color[ver] = x;
for (int i = head[ver]; i != -1; i= pra[i].next){
int u = pra[i].to;
if (bccno[u] != b)
continue;
if (color[u] < 0){
if (!bi(u,b,x^1) )
return 0;
}else{
if (color[u]!= x^1)
return 0;
}
}
return 1;
}
int main()
{
freopen("input.txt","r",stdin);
int N,M;
while (~scanf("%d %d",&N,&M)){
if (N == 0 && M == 0)
break;
init();
int tempx,tempy;
for (int i = 1; i <= M; i++){
scanf("%d %d",&tempx,&tempy);
ishate[tempx][tempy] = 1;
ishate[tempy][tempx] = 1;
}
for (int i = 1; i <= N; i++)
for (int j = i+1; j <= N; j++){
if (ishate[i][j] != 1){
addedge2(i,j);
}
}
find_bcc(N);
memset(odd,-1,sizeof(odd));
for (int i = 1; i <= bcc_cnt; i++){//bcc_cnt表示双连通分量的个数
memset(color, -1,sizeof(color));
for (int j = 0; j < bcc[i].size(); j++)
bccno[bcc[i][j]] = 1;
int u = bcc[i][0];
color[u] = 1;
if (!bi(u,i,1)){
//printf("i=%d first = %d\n",i,u);
for (int j = 0; j <bcc[i].size(); j++)
odd[bcc[i][j]] = 1;
}
}
int res = 0;
for (int i = 1; i <= N; i++){
if (odd[i] < 0)
res++;
}
printf("%d\n",res);
}
return 0;
}