圆桌骑士,蓝书P316LA3523(tarjan求双连通分量算法,二分图判断,奇圈)

重点重点重点,每一个不在环内的边本身也是单独的一个双连通分量。双连通分量定义:若一个无向图中的去掉任意一个节点(一条边)都不会改变此图的连通性,即不存在割点(桥),则称作点(边)双连通图。刘汝佳白书P314也给出了这个等价定义,但是“任意两点之间至少存在两条点不重复的路径”这个定义明显不包含单独边这种情况。tarjan算法都是基于标准定义的。

本题目的经典程度不用多赘述。直接总结:
1.Edge e = (Edge){u, v};//此处语法细节需要学习 结构体 的初始化方式。
2.if(fa < 0 && child == 1) iscut[u] = 0;//当fa<0且child == 1的时候,就是一条边,对于一条边无割顶
3.bccno数组标记当前访问的连通分量的点。
4.用color数组来判断是否为二分图。

// LA3523 Knights of the Round Table
// Rujia Liu
#include<cstdio>
#include<stack>
#include<vector>
#include<algorithm>
#include<cstring>

using namespace std;

struct Edge { int u, v; };

const int maxn = 1000 + 10;
int pre[maxn], iscut[maxn], bccno[maxn], dfs_clock, bcc_cnt; // 割顶的bccno无意义
vector<int> G[maxn], bcc[maxn];

stack<Edge> S;

int dfs(int u, int fa) {
  int lowu = pre[u] = ++dfs_clock;
  int child = 0;
  for(int i = 0; i < G[u].size(); i++) {
    int v = G[u][i];
    Edge e = (Edge){u, v};//此处语法细节需要学习 
    if(!pre[v]) { // 没有访问过v
      S.push(e);
      child++;
      int lowv = dfs(v, u);
      lowu = min(lowu, lowv); // 用后代的low函数更新自己
      if(lowv >= pre[u]) {
        iscut[u] = true;
        bcc_cnt++; 
        for(;;) {
          Edge x = S.top(); S.pop();
          if(bccno[x.u] != bcc_cnt) { bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt; }
          if(bccno[x.v] != bcc_cnt) { bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt; }
          if(x.u == u && x.v == v) break;
        }
      }
    }
    else if(pre[v] < pre[u] && v != fa) {
      S.push(e);
      lowu = min(lowu, pre[v]); // 用反向边更新自己
    }
  }
  if(fa < 0 && child == 1) iscut[u] = 0;//当fa<0且child == 1的时候,就是一条边,对于一条边无割顶 
  return lowu;
}

void find_bcc(int n) {
  // 调用结束后S保证为空,所以不用清空
  memset(pre, 0, sizeof(pre));
  memset(iscut, 0, sizeof(iscut));
  memset(bccno, 0, sizeof(bccno));
  dfs_clock = bcc_cnt = 0;
  for(int i = 0; i < n; i++)
    if(!pre[i]) dfs(i, -1); 
}

int odd[maxn], color[maxn];
bool bipartite(int u, int b) {
  for(int i = 0; i < G[u].size(); i++) {
    int v = G[u][i]; if(bccno[v] != b) continue;
    if(color[v] == color[u]) return false;
    if(!color[v]) {
      color[v] = 3 - color[u];
      if(!bipartite(v, b)) return false;
    }
  }
  return true;
}

int A[maxn][maxn];

int main() {
  int kase = 0, n, m;
  while(scanf("%d%d", &n, &m) == 2 && n) {
    for(int i = 0; i < n; i++) G[i].clear();

    memset(A, 0, sizeof(A));
    for(int i = 0; i < m; i++) {
      int u, v;
      scanf("%d%d", &u, &v); u--; v--; A[u][v] = A[v][u] = 1;
    }
    for(int u = 0; u < n; u++)
      for(int v = u+1; v < n; v++)
        if(!A[u][v]) { G[u].push_back(v); G[v].push_back(u); }//!!!图的邻接存储!!! 

    find_bcc(n);

    memset(odd, 0, sizeof(odd));
    for(int i = 1; i <= bcc_cnt; i++) {
      memset(color, 0, sizeof(color));
      for(int j = 0; j < bcc[i].size(); j++) bccno[bcc[i][j]] = i; // 将所有节点暂时标记为当前要访问的双连通分量,主要是处理割顶
      int u = bcc[i][0];
      color[u] = 1;//二分图一定不是简单奇圈,简单奇圈一定不是二分图 ,刘巧妙用color变量来判断是否为二分图 
      if(!bipartite(u, i))
        for(int j = 0; j < bcc[i].size(); j++) odd[bcc[i][j]] = 1;
    }
    int ans = n;
    for(int i = 0; i < n; i++) if(odd[i]) ans--;
    printf("%d\n", ans);
  }
  return 0;
}
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/NCUscienceZ/article/details/78534392
上一篇猜序列,白书P309LA4255(暂放)
下一篇井下矿工,白书P318LA5131(点双连通分量应用)
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭