点连通分量-poj-2942Knights of the Round Table



点连通分量的例题

贴一下点连通分量的模板



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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值