點連通分量-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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章