点双连通 练习

某个图的点双联通分支里面必然是没有割点存在的, 在搜索树中不停地找,如果找到割点
,我们便将栈中的所有边都找出来,然后放进一个点双连通块中即可。割点可以属于多个双连通块,
但是其他点和边只能属于一个块.

原题就是 亚瑟王要骑士开会, 给一些边,边链接两个骑士,这两个骑士不能坐一起,问最少要减去多少骑士
建立补图,表示边连接的两个骑士可以坐在一起。然后我们要找点连通块中的奇数圈,定理写在代码中了。如果某个骑士
不存在奇数圈中,那么他就不能参加会议。

关于二分图判断问题,用染色法 dfs一下就好了

#include <cstdio>
#include <cmath>
#include <cstring>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <sstream>
#include <queue>
#include <typeinfo>
#include <fstream>
#include <map>
#include <stack>
typedef long long ll;
using namespace std;

const int MAXN = 1010;//点数
const int MAXM = 2000005;//边数,因为是无向图,所以这个值要*2

struct Edge
{
    int to,next;
}edge[MAXM];
int head[MAXN],tot;
int Low[MAXN],DFN[MAXN],Stack[MAXN],Belong[MAXN];
int Index,top;
bool Instack[MAXN];
int block; //点双连通块数量
//int cut[MAXN]// 记录割点

bool ok[MAXN];
bool can[MAXN];  //标记
int tmp[MAXN]; //存点双连通分量中的点
int cc;  //记录 这个连通块中有多少个点
int color[MAXN]; //二分图染色
void addedge(int u,int v)
{
    edge[tot].to = v;edge[tot].next = head[u];
    head[u] = tot++;
}

bool dfs(int u,int col){  //染色法判二分图
    color[u]=col;
    for(int i=head[u];i!=-1 ; i=edge[i].next){
        int v=edge[i].to;
        if( !ok[v]) continue;   //如果不在这个联通块中,可以跳过
        if(color[v]!= -1){ //若v染过色 ,(初值设为-1)
            if(color[v]==col) //并且和相邻点颜色相同
                return false;
            continue; //否则继续
        }
        if(!dfs(v,!col))   //!-1  =0 ;
            return false;
    }
    return true;
}
void Tarjan(int u,int pre)
{
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
//    int son=0;    //  统计某个点在搜索树中的儿子个数
    for(int i = head[u];i != -1;i = edge[i].next)
    {
        v = edge[i].to;
        if(v == pre) continue;
        if( !DFN[v] )
        {
            Tarjan(v,u);
            if( Low[u] > Low[v] )  Low[u] = Low[v];
            //一条边 (u,v)是桥,当且仅当(u,v)为树枝边(u,v都在栈里面),且满足DFN(u)<Low(v)

            //这个地方就是对点双连通快进行操作
            //
            //
            if(u!=pre && Low[v] >= DFN[u] ) //   若找到 割点u ,把边一一取出直到遇见 (u,v) 
            {
                block++;
                int vn;
                cc=0;
                memset(ok,0,sizeof(ok));
                do{
                    vn=Stack[--top];
                    Belong[vn]=block;
                    Instack[vn]=0;
                    ok[vn]=1;         // 表示这个点是在连通块里面的
                    tmp[cc++]=vn;
                }
                while(vn!=v); // 取完
                ok[u]=1;
                memset(color,-1,sizeof(color));  //初值为-1
                //将u点染色为0,判断能否形成二分图
                if(!dfs(u,0)){    //定理1:若在该连通分量里面不可形成二分图 ,若为二分图则必有奇数圈,反之也成立
                    can[u]=1;
                    while(cc--)   //定理 2 : 若一个双联通分中某些点在奇数圈中,则所有点也在某个奇数圈中。
                        can[tmp[cc]]=1;
                }

            }
        }
        else if( Instack[v] && Low[u] > DFN[v] ) //若
            Low[u] = DFN[v];
    }
}
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
void solve(int n){
    memset(DFN,0,sizeof(DFN));
    memset(Instack,false,sizeof(Instack));
//    memset(add_block,0,sizeof(add_block) );
//    memset(cut,false,sizeof(cut) );
    memset(can,0,sizeof(can));
    Index=top=block=0;
    for(int i=1;i<=n;i++)
        if(!DFN[i])
           Tarjan(i,0);
//    printf("blo=%d\n",block);
    int ans=n;
    for(int i=1;i<=n;i++)
       if(can[i])
           ans--;
    printf("%d\n",ans);
}
int arc[MAXN][MAXN];
int main(){
    int n,m;
    //freopen("1.txt","r",stdin);
    while(~scanf("%d %d",&n,&m) &&(n||m) ){
         init();
         int u,v;
         memset(arc,0,sizeof(arc));
         for(int i=0;i<m;i++){
             scanf("%d %d",&u,&v);
             arc[u][v]=arc[v][u]=1;
         }
         for(int i=1;i<=n;i++)
             for(int j=1;j<i;j++){
                 if(arc[i][j]==0){
//                   printf("%d %d\n",i,j);
                     addedge(i,j);
                     addedge(j,i);
                 }
             }
         solve(n);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值