pku2942 (点)双连通分量 求割点 判奇圈

Knights of the Round Table

这题还是比较难了,我刷了两天,终于弄懂什么意思了,杯具!
点双连通,指的是,去掉任何一个点以及这个点的临边,都不影响整个图的连通性。可以这样认为,任意两点都有两条完全不同的路可达,也就构成了圈。

题目大意:国王身边有n个骑士,每次开会需要奇数个骑士来开会,会议桌是个圆桌,给出m个点对<A, B>,表示骑士A和B不能坐在一起,问:有多少个骑士,无论如何,不可能坐到会议桌旁,则国王就会开除他们。

分析:想一想,如果骑士A可以做到圆桌旁,得满足两个条件:1. 圆桌上的有奇数个人。2. 圆桌每两个相邻的骑士不hate对方。就可以建一个补图,顶点集合为所有的骑士,如果任意一对骑士可以坐在一起,则可以在图上加上一条边。则如果能找到一个奇圈,则这个圈里的所有人都符合条件,都可以留下。如果某一个骑士,不在任何一个奇圈内,则会被国王开除。

既然是圈,就表示这个圈所表示的子图是个双连通的,而又有一个定理是这样描述的,对于一个(点)双连通分量,如果找到一个奇圈,则这个分量的其他点也必然在某一个奇圈内。这样就好做了,只要找到图的割点,找到所有的双连通分支,判断一下分支中是否存在奇圈,如果存在,则整个分支的点都可以标记为留下,最后那总的点个数,减去可以留下的,剩下的就是要被国王开除的。图的割点可能存在于两个连通分支分支中,但缩点后割点和分支不会再构成双连通分量,也就不会出现分支跨度大于1的奇圈。

上面那个定理只适用于点双连通,不用于边双连通。看图
1           4

| \       /   \ 

|   \   /       \

|     2          5

|   /   \       /

| /       \   /

3          6

点2是割点,就会形成两个双连通分支(1, 2, 3), (2, 4, 5 , 6),然后用定理可以判断出分支1有奇圈,分支2没有,ans = 6 - 3 = 3。图中没有割边,整个图就一个连通分支,如果用定理就是任意一个点都在某奇圈内,显然4, 5, 6 不可能。所以这个定理用于点双连通。既然如此,这题就用求割点的方法就可以过了。

一个顶点u是割点,当且仅当满足(1)或(2)
(1) u为树根,且u有多于一个子树。
(2) u不为树根,且满足存在(u,v)为树枝边(或称父子边,即u为v在搜索树中的父亲),使得DFN(u)<=Low(v)。

这里如果是第一种情况,显然去掉树根,就会形成两棵树,也就是说连通分支增加了,u当然就是割点了。

第二种情况,<u, v>为树枝边,如果dfn(u) <= low[v],说明v以及v的子树最多会有反向边连在u上,绝对不会连通u以前的结点,所以,去掉u后,连通分支增加了,所以u就是割点。

这道题求得是连通分支中的奇圈,第一种情况可以忽略,可以不用考虑,如果第一个点只有一个树枝边,也就是说,第一个点的度为1,那么他就不是割点,但对于这道题没有多大影响,这道题直接用dfs是由于u作为根节点,DFN(u)肯定小于LOW(v),当误判为割点的时候,也不会找到奇圈,最后还是会被去掉,一个结点不算奇圈的。

在判奇圈的时候有这样一个定理:一个图是二部图当且仅当它不包含奇数环。这样我们就可以先假设是二分图,用交叉染色的方法,初始化一对相邻点异色,依边染色,如果有一点未被染色,相邻点染成异色,如果遇到相邻点都已染色,且为同色,则证明有奇圈,如果,染完整个连通分支都没有遇到相邻点异色的话,就证明没有奇圈。

在处理连通分支的时候,可以在dfs的时候就用栈来保存边,类似Tarjan的方法,如果遇到DFN(U) <= LOW(V),则u为割点,边出栈即可。

#include<stdio.h>

#include<string.h>

#define NN 1004

#define MM 1000004

int map[NN][NN];

int dfn[NN]; // 深搜标号 

int low[NN]; // 能搜到的最小标号 

int col[NN];

int odd[NN]; // 保存任意在奇圈的点 

int mark[NN];

int idx, n, time, top;



typedef struct node{

	int v, vis;

	struct node *nxt, *op;// op表示反向边 

}NODE;



NODE edg[MM];

NODE *link[NN];

NODE *stack[MM];

int Min(int a, int b){

    return a < b ? a : b;    

}



void Add(int u, int v){// 加边 

	edg[idx].v = v;

	edg[idx].vis = 0;

	edg[idx].nxt = link[u];

	edg[idx].op = edg + idx + 1;

	link[u] = edg + idx++;

	edg[idx].v = u;

	edg[idx].vis = 0;

	edg[idx].nxt = link[v];

	edg[idx].op = edg + idx - 1;

	link[v] = edg + idx++;

}



int find(int u){//用交叉染色的方法查找奇圈 

	int v;

	for (NODE *p = link[u]; p; p = p->nxt){

		v = p->v;

		if (mark[v]){

			if (col[v] == -1){

				col[v] = !col[u];

				return find(v);

			}else if (col[v] == col[u]){// 如果相邻两点同色,说明有奇圈 

				return 1;

			}

		}

	}

	return 0;

}



void Col(int u){// 取出分支的点,并着色 

	int i;

	NODE *p;

	memset(mark, 0, sizeof(mark));

	do{// 先将分支中的点用mark[]标记一下 

		 p = stack[top];

		 mark[p->v] = 1;

		 mark[p->op->v] = 1;

		 top--;

	}while(p->op->v != u);

	memset(col, -1, sizeof(col));

	col[u] = 0;

	if (find(u)){// 如果有奇圈 

		for (i = 1; i <= n; i++){

			if (mark[i]) odd[i] |= 1; // 如果点i在任意奇圈内,都可以用odd[]标记 

		}

	}

}



void dfs(int u){

	int v;

	dfn[u] = low[u] = ++time;

	for (NODE *p = link[u]; p; p = p->nxt){

		v = p->v;

		if (p->vis) continue;

		p->vis = p->op->vis = 1; // 将反向边也置1,无向边只走一次 

		stack[++top] = p; 

		if (!dfn[v]){

            dfs(v);

            low[u] = Min(low[u], low[v]);

            if (dfn[u] <= low[v]){ // u为割点的条件 

				Col(u);

			}

		}else{

            low[u] = Min(low[u], dfn[v]);

		}

	}

}



void Solve(){

	int cnt, i;

	time = 0;

	top = 0;

	memset(odd, 0, sizeof(odd));

	memset(dfn, 0, sizeof(dfn));

	memset(low, 0, sizeof(low));

	for (i = 1; i <= n; i++)

		if(!dfn[i]) // 图有可能不连通 

			dfs(i);

		cnt = 0;

		for (i = 1; i <= n; i++){

			if (odd[i]) cnt++;

		}

		printf("%d\n", n - cnt);

}



int main()

{

    int m, i, j, u, v;

    while(scanf("%d%d", &n, &m) != EOF){

		if (n == 0 && m == 0) break;

		for(i = 1; i <= n; i++){

			for(j = 1; j <= n; j++){

				map[i][j] = 1;

			}

		}

		

		while(m--){

			scanf("%d%d", &u, &v);

			map[u][v] = map[v][u] = 0;

		}

		

		memset(link, 0, sizeof(link));

		for(i = 1; i <= n; i++){// 建补图 

			for(j = 1; j < i; j++){

				if (map[i][j] == 1) Add(i, j);

			}

		}

		Solve();

    }

    return 0;

}

你可能感兴趣的:(pku)