跳转至

点双连通分量

点双连通分量指的是在一个无向图中,极大的一组顶点和边,使得任意两个顶点间都有至少两条不相交的路径(即即使删除图中的一个顶点,这些顶点仍然是连通的)。换句话说,点双连通分量中的任意两个顶点间都是点双连通的。

例题 #1

对于一个 \(n\) 个节点 \(m\) 条无向边的图,请输出其点双连通分量的个数,并且输出每个点双连通分量。

提示&代码

注意一个点可能属于多个vdcc,所以不可用belong[]存储,而应该直接推入(vector) vdcc[cnt]

完整代码

/*////////ACACACACACACAC///////////
       . Code  by  Ntsc .
       . Earn knowledge .
/*////////ACACACACACACAC///////////

#include<bits/stdc++.h>
#define int long long
#define db double
#define rtn return
using namespace std;

const int N=5e5+5;
const int M=5e6;
const int Mod=1e5;
const int INF=1e5;

int n,m,p,T;



int dfn[N],low[N],indx;//时间戳,追溯值,给时间戳编号的计数器 
int stk[N],tp;//栈,指针 
bool instk[N]; //是否在栈中
int belong[N],siz[N],vdcc; //记录每个点在那个边双连通分量里, 每个点所在的边双连通分量的大小,边双连通分量的数量 
vector<int> ans[N];

int vis[N];

struct edge{
    int to,nxt;
}e[M];
int h[N],idx=-1;
void add(int a,int b){
    e[++idx]={b,h[a]};
    h[a]=idx;
    e[++idx]={a,h[b]};
    h[b]=idx;
}

void tarjan(int u,int fa) {//当前节点,入边的节点编号
    int son=0;//判孤立点用 
    vis[u]=1;
    dfn[u]=low[u]=++indx;
    stk[++tp]=u;
    for(int i=h[u]; ~i; i=e[i].nxt) {

        int v=e[i].to;
        if(dfn[v]==0) {
            son++;
            tarjan(v,u);//not tarjan(v,i);
            low[u]=min(low[u],low[v]);
            ///new
            if(low[v]>=dfn[u]){
                vdcc++;
                int x;
                do {
                    x=stk[tp--];
                    ans[vdcc].push_back(x);//注意!一个点可能属于多个vdcc! 
                } while(x!=v);
                ans[vdcc].push_back(u);
            }
        }
        else if(v!=fa) low[u]=min(low[u],dfn[v]);//判断点是否相同,而边双中是判断边是否和来时的边的反向边相同,都是为了防止回头 
    }
    if(!(fa||son))ans[++vdcc].push_back(u);//特判孤立点 
}
signed main(){
    int m,n;
    cin>>n>>m;

    for(int i=1;i<=N;i++)e[i].nxt=-1,h[i]=-1;
    for(int i=1;i<=m;i++){
        int a,b;
        cin>>a>>b;
        add(a,b);
    }
    for(int i=1;i<=n;i++)if(!vis[i])tp=0,tarjan(i,0);//图可能不连通!切勿重复访问!

    cout<<vdcc<<endl; 
    for(int i=1;i<=vdcc;i++){
        cout<<ans[i].size()<<' ';
        for(auto v:ans[i])cout<<v<<' ';
        cout<<endl;
    }
    return 0;
}

例题 #2 KNIGHTS - Knights of the Round Table

题目大意

\(n\) 个骑士经常举行圆桌会议,商讨大事。每次圆桌会议至少有 \(3\) 个骑士参加,且相互憎恨的骑士不能坐在圆桌的相邻位置。如果发生意见分歧,则需要举手表决,因此参加会议的骑士数目必须是大于 \(1\) 的奇数,以防止赞同和反对票一样多。知道骑士之间相互憎恨的关系后,请你帮忙统计有多少骑士参加不了任意一个会议。

输入格式

本题包含多组数据。

对于每组数据, 第一行为两个整数 \(n\)\(m\)

接下来 \(m\) 行每行两个整数 \(k_1\)\(k_2\),表示骑士 \(k_1\)\(k_2\) 相互憎恨。

\(n=m=0\) 为数据末尾的标记,无需处理这组数据。

输出格式

对于每组数据,输出一行一个整数,表示无法参加任何会议的骑士数量。

感谢@hicc0305 提供的翻译

数据范围与提示

\(1\leq n \leq 10^3\)\(1\leq m\leq10^6\)。保证 \(1\leq k_1,k_2\leq n\)

\(\small{\text{Statement fixed by @Starrykiller.}}\)


我们考虑先建补图,那么我们就要求每个骑士都至少在一个奇环(点数为奇数)中。

那么怎么样判断是否在奇环中呢?我们有一个结论:

一个vdcc如果存在奇环,那么这个vdcc内的每个点都在一个奇环上 。

于是我们使用二分图染色对每个vdcc判奇环

/*                                                                                
                      Keyblinds Guide
                    ###################
      @Ntsc 2024

      - Ctrl+Alt+G then P : Enter luogu problem details
      - Ctrl+Alt+B : Run all cases in CPH
      - ctrl+D : choose this and dump to the next
      - ctrl+Shift+L : choose all like this
      - ctrl+K then ctrl+W: close all
      - Alt+la/ra : move mouse to pre/nxt pos'

*/
#include <bits/stdc++.h>
#include <queue>
using namespace std;

#define rep(i, l, r) for (int i = l, END##i = r; i <= END##i; ++i)
#define per(i, r, l) for (int i = r, END##i = l; i >= END##i; --i)
#define pb push_back
#define mp make_pair
#define int long long
#define ull unsigned long long
#define pii pair<int, int>
#define ps second
#define pf first

// #define innt int
#define itn int
// #define inr intw
// #define mian main
// #define iont int

#define rd read()
int read(){
    int xx = 0, ff = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            ff = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
      xx = xx * 10 + (ch - '0'), ch = getchar();
    return xx * ff;
}
void write(int out) {
    if (out < 0)
        putchar('-'), out = -out;
    if (out > 9)
        write(out / 10);
    putchar(out % 10 + '0');
}

#define ell dbg('\n')
const char el='\n';
const bool enable_dbg = 1;
template <typename T,typename... Args>
void dbg(T s,Args... args) {
    if constexpr (enable_dbg){
    cerr << s;
    if(1)cerr<<' ';
        if constexpr (sizeof...(Args))
            dbg(args...);
    }
}

#define zerol = 1
#ifdef zerol
#define cdbg(x...) do { cerr << #x << " -> "; err(x); } while (0)
void err() { cerr << endl; }
template<template<typename...> class T, typename t, typename... A>
void err(T<t> a, A... x) { for (auto v: a) cerr << v << ' '; err(x...); }
template<typename T, typename... A>
void err(T a, A... x) { cerr << a << ' '; err(x...); }
#else
#define dbg(...)
#endif


const int N = 2e3 + 5;
const int INF = 1e3;
const int M = 200;
const int MOD = 1e9 + 7;




vector<int> e[N];
void add(int a,int b){
    e[a].pb(b);
    e[b].pb(a);
}



int dfn[N],low[N];
int tim;
int stk[N],top;

bitset<N> vis,flg,col;
vector<int> ans[N];

int vdcc;
int res;


void tarjan(int x,int fa){
    dfn[x]=low[x]=++tim;
    stk[++top]=x;
    int son=0;
    for(auto v:e[x]){
        if(v==fa)continue;
        if(!dfn[v]){
            son++;
            tarjan(v,x);
            low[x]=min(low[x],low[v]);
            if(low[v]>=dfn[x]){
                vdcc++;
                int y;
                do{
                    y=stk[top--];
                    ans[vdcc].pb(y);
                }while(y!=v);//!!

                ans[vdcc].pb(x);
            }

        }else low[x]=min(low[x],dfn[v]);
    }

    if(!(fa||son))ans[++vdcc].pb(x);
}


queue<int> q;

bool bfs(int s){
    q.push(s);
    vis[s]=1;
    while(q.size()){
        int x=q.front();
        // cdbg(x);
        q.pop();
        for(auto v:e[x]){
            if(!flg[v])continue;
            if(vis[v]){
                if(col[x]==col[v])return 0;
                continue;
            }
            col[v]=col[x]^1;
            vis[v]=1;
            q.push(v);
        }

    }

    return 1;
}


bool d[N][N];
bitset<N> able;
int n,m;

void init(){
    for(int i=1;i<=n;i++){
        while(e[i].size())e[i].pop_back();
    }

    memset(d,0,sizeof d);
    memset(dfn,0,sizeof dfn);
    memset(low,0,sizeof low);
    tim=0;
    top=0;

    for(int i=1;i<=vdcc;i++){
        while(ans[i].size())ans[i].pop_back();
    }


    vdcc=0;
}

void solve(){

    init();
    n=rd,m=rd;


    if(n+m==0)exit(0);
    for(int i=1;i<=m;i++){
        d[rd][rd]=1;
    }


    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(!d[i][j]&&!d[j][i])add(i,j);
        }
    }
    //判断一个点是否在一个奇数环内
    //结论:一个vdcc如果存在奇环,那么这个vdcc内的每个点都在一个奇环上
    //使用二分图染色判奇环


    // cdbg("OK");
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            tarjan(i,0);
        }
    }




    // for(int i=1;i<=vdcc;i++){
    //  for(auto v:ans[i])dbg(v);
    //  ell;
    // }


    able.reset();

    for(int i=1;i<=vdcc;i++){
        for(auto v:ans[i])flg[v]=1;
        if(bfs(ans[i].front())){
            // res+=ans[i].size();
            // cdbg("ban",i);
        }else{
            for(auto v:ans[i])able[v]=1;
        }
        for(auto v:ans[i])flg[v]=0,vis[v]=0;
    }

    res=0;

    for(int i=1;i<=n;i++)res+=(able[i]==0);

    cout<<res<<endl;

}



signed main() {
//     freopen("P2619_3.in","r",stdin);
    // freopen("center.out","w",stdout);

    int T=1;
    while(T){
        solve();
    }
    return 0;
}