KNIGHTS - Knights of the Round Table 圆桌骑士 点双 + 二分图判定

~~~题面~~~

题解:

  考场上只想到了找点双,,,,然后不知道怎么处理奇环的问题。

  我们考虑对图取补集,这样两点之间连边就代表它们可以相邻, 那么一个点合法当且仅当有至少一个大小至少为3的奇环经过了它。

  观察到只会出现一棵类似树的结构 + t个相对独立的环, 因为环肯定都是独立出来的,所以可以不用管它。

  因此我们先找出所有点双,然后判断这个点双内是否有奇环,用二分图染色来判断。如果有奇环,则说明这个点双内的所有点都可以出现在一个奇环上,反之则都不会出现。

  所以我们只需要寻找一下点双,然后判断是否合法并加上相应贡献即可。

  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 }
View Code

 

转载于:https://www.cnblogs.com/ww3113306/p/9896354.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值