KNIGHTS - Knights of the Round Table 圆桌骑士 点双 + 二分图判定
题解:
考场上只想到了找点双,,,,然后不知道怎么处理奇环的问题。
我们考虑对图取补集,这样两点之间连边就代表它们可以相邻, 那么一个点合法当且仅当有至少一个大小至少为3的奇环经过了它。
观察到只会出现一棵类似树的结构 + t个相对独立的环, 因为环肯定都是独立出来的,所以可以不用管它。
因此我们先找出所有点双,然后判断这个点双内是否有奇环,用二分图染色来判断。如果有奇环,则说明这个点双内的所有点都可以出现在一个奇环上,反之则都不会出现。
所以我们只需要寻找一下点双,然后判断是否合法并加上相应贡献即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 1100 5 #define ac 11000000 6 7 int n, m, cnt, timer, ans; 8 int q[AC], top; 9 int q1[AC], head, tail; 10 int belong[AC], low[AC], color[AC], dfn[AC]; 11 bool z[AC][AC], vis[AC]; 12 13 inline int read() 14 { 15 int x = 0;char c = getchar(); 16 while(c > '9' || c < '0') c = getchar(); 17 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 18 return x; 19 } 20 21 void pre() 22 { 23 n = read(), m = read(); 24 if(!n && !m) exit(0); 25 memset(belong, 0, sizeof(belong)); 26 memset(vis, 0, sizeof(vis)); 27 memset(dfn, 0, sizeof(dfn)); 28 timer = cnt = ans = top = 0; 29 memset(z, 0, sizeof(z)); 30 int a, b; 31 for(R i = 1; i <= m; i ++) 32 { 33 a = read(), b = read(); 34 z[a][b] = z[b][a] = true; 35 } 36 for(R i = 1; i <= n; i ++) z[i][i] = true;//不能走自环 37 } 38 39 inline void upmin(int &a, int b){ 40 if(b < a) a = b; 41 } 42 43 bool check(int x)//检查x所在点双是否是一个二分图 44 { 45 memset(color, 0, sizeof(color)); 46 head = tail = 0; 47 q1[++tail] = x, color[x] = 1; 48 while(head < tail) 49 { 50 x = q1[++head]; 51 for(R i = 1; i <= n; i ++) 52 { 53 if(!z[x][i] && belong[i] == cnt)//如果有边并且在同一个点双中 54 { 55 if(color[x] == color[i]) return false; 56 else if(!color[i]) q1[++tail] = i, color[i] = color[x] ^ 3; 57 } 58 } 59 } 60 return true; 61 } 62 63 void tarjan(int x, int fa)//求点双联通分量 64 { 65 low[x] = dfn[x] = ++ timer; 66 for(R i = 1; i <= n; i ++)//枚举边 67 { 68 if(!z[x][i]) 69 { 70 if(!dfn[i]) 71 { 72 q[++top] = i;//将这个点加入栈 73 tarjan(i, x); 74 upmin(low[x], low[i]); 75 if(low[i] >= dfn[x])//这个点是割点 76 { 77 int tot = 2;//先加上割点和当前now的tot 78 belong[x] = ++ cnt;//先给这个割点临时打上标记 79 while(q[top] != i) ++tot, belong[q[top --]] = cnt;//记录下这个点所属的点双联通分量 80 belong[q[top --]] = cnt; 81 if(!check(x))//不是二分图,那么这个bcc当中的点都是合法的 82 { 83 int b = top + tot - 1;//直接把刚取出来的点打上标记 84 vis[x] = true;//q[top] 不一定是割点 85 for(R i = top + 1; i <= b; i ++) vis[q[i]] = true; 86 } 87 } 88 } 89 else if(i != fa) upmin(low[x], dfn[i]); 90 } 91 } 92 } 93 94 void work() 95 { 96 while(1) 97 { 98 pre(); 99 for(R i = 1; i <= n; i ++) 100 if(!dfn[i]) q[++top] = i, tarjan(i, i);//将当前点加入栈 101 for(R i = 1; i <= n; i ++) if(!vis[i]) ++ans; 102 printf("%d\n", ans); 103 } 104 } 105 106 int main() 107 { 108 // freopen("in.in", "r", stdin); 109 work(); 110 // fclose(stdin); 111 return 0; 112 }