点双联通 + 二分图染色~~
这道题做了有两天了,因为点双联通一直写不对,昨天就又花了一些时间看了下割点,块,割边,缩点,今天在贡献了n次wa后终于AC了,泪奔~~
题目大意:有n个骑士,有m对憎恨关系,现在要选择一些骑士(奇数个)围着一个圆桌坐下来,相邻的骑士之间不能相互憎恨,问有多少个骑士永远不可能入座!!
思路:先建图,相互补憎恨的骑士之间连一条边,之后问题就转化成求那些骑士永远也不可能在一个奇圈内,先求块, 把图化成很多的块,然后在一个块中
判断是否会存在奇圈。如果存在奇圈,则块中所有的点都可以在一个奇圈内。这里用二分图来判断块中是否含有奇圈,因为如果存在奇圈的话,则该块所构成的图
一定不是一个二分图,二分图绕一圈肯定是偶数条边。 因为割点可能会存在于多个块中,所有要把每个点用数组记录下它是否在一个奇圈内!!
/*
总之这题的题意还是很清晰的吧。首先很容易想到要把这个图取补图,然后判定每个点能否在奇圈中出现。
关键是第二步如何做。这时我们就想到了点双连通分量的概念。何为点双连通分量?简而言之就是分两中任意两点之间存在两条点不重复的路径。注意是点不是边。我当时就是WA在这里了,一直当边双连通怎么也算不对。。。其实还是因为没有完全理解而已。
那么点双连通分量有神马特点呢?在这个分量里,任取出一些点都能构成一个环。这个灰常关键,但是现在只是找到了环,而题目要求的是奇数环。
这时就要用到一个定理了,对于一个点双连通分量,在分量中存在奇环当且仅当这个分量不是二分图。直观的想,对于一个二分图,从一个点出发要回到一个点显然要经过偶数个节点。所以肯定不存在奇圈。
那么这仅仅是在分量里有一个奇圈,这能有什么用呢?别急。仔细考虑点双连通分量的性质,如果点双连通分量有奇数个节点,那么整个分量就是个奇圈;如果分量里有偶数个节点,那么把存在的那个奇圈刨去,剩下的点也必然能构成奇圈。因此只要在点双连通分量里有一个奇圈,那么所有点就都能够在奇圈上出现了。
那么算法就显而易见了。
首先求出图的补图,然后把点双连通分量找出,对于每个双连通分量判断是否为二分图,如果不是则将分量中所有点标记,统计标记过的节点个数x,最终结果就是n-x。
*/
代码:
1 # include<stdio.h>
2 # include<string.h>
3 # define N 1005
4 # define M 1000005
5 # include<stack>
6 using namespace std;
7 struct node{
8 int from,to,next;
9 }edge[2*M];
10 stack<int>S;
11 bool adj[N][N],visit[N],vis[N];
12 int tol,head[N],count,cnt,flag,n,m,val[N],dfn[N],low[N],color[N],Belong[N];
13 void add(int a,int b)
14 {
15 edge[tol].from=a;edge[tol].to=b;edge[tol].next=head[a];head[a]=tol++;
16 }
17 int min(int a,int b)
18 {
19 return a<b?a:b;
20 }
21 void dfs(int root,int col)//判断该块是不是一个二分图
22 {
23 int j,v;
24 color[root]=col;
25 for(j=head[root];j!=-1;j=edge[j].next)
26 {
27 v=edge[j].to;
28 if(Belong[v]!=count) continue;
29 if(color[v]==1-col) continue;
30 if(color[v]==-1) dfs(v,1-col);
31 else if(color[v]==col) flag=1;
32 if(flag) return;
33 }
34 }
35 void tarjan(int u,int father)//点双联通 求 块
36 {
37 int j,v,ss,i,ans;
38 dfn[u]=low[u]=cnt++;
39 visit[u]=1;
40 S.push(u);
41 for(j=head[u];j!=-1;j=edge[j].next)
42 {
43 v=edge[j].to;
44 if(v==father) continue;
45 if(!visit[v])
46 {
47 tarjan(v,u);
48 low[u]=min(low[u],low[v]);
49 if(low[v]>=dfn[u]) //u是一个割点
50 {
51 count++;
52 ans=0;
53 do{
54 ss=S.top();
55 S.pop();
56 val[++ans]=ss;
57 Belong[ss]=count;
58 }while(ss!=v);//这个只能弹到v,u是割点,可能存在与多个块中
59 Belong[u]=count;
60 val[++ans]=u;
61 flag=0;
62 memset(color,-1,sizeof(color));
63 dfs(u,0);
64 if(flag)
65 {
66 for(i=1;i<=ans;i++)
67 vis[val[i]]=1;
68 }
69 }
70 }
71 else low[u]=min(low[u],dfn[v]);
72 }
73 }
74 int main()
75 {
76 int i,j,a,b,sum;
77 while(scanf("%d%d",&n,&m)!=EOF)
78 {
79 if(n==0 && m==0) break;
80 memset(adj,0,sizeof(adj));
81 tol=count=cnt=0;
82 for(i=0;i<m;i++)
83 {
84 scanf("%d%d",&a,&b);
85 adj[a][b]=adj[b][a]=1;
86 }
87 memset(head,-1,sizeof(head));
88 for(i=1;i<=n;i++)
89 {
90 for(j=i+1;j<=n;j++)
91 if(adj[i][j]==0)
92 {
93 add(i,j);
94 add(j,i);
95 }
96 }
97 memset(Belong,-1,sizeof(Belong));//少加一个初始化,WA到无语。。。
98 memset(visit,0,sizeof(visit));
99 memset(vis,0,sizeof(vis));
100 for(i=1;i<=n;i++)
101 if(!visit[i]) tarjan(i,-1);
102 sum=0;
103 for(i=1;i<=n;i++)
104 if(vis[i]) sum++;
105 printf("%d\n",n-sum);
106 }
107 return 0;
108 }