点双连通 练习

来源:互联网 发布:linux cat 显示文件名 编辑:程序博客网 时间:2024/04/28 08:02

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

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

关于二分图判断问题,用染色法 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;//边数,因为是无向图,所以这个值要*2struct 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;}
0 0