[Nowcoder] network | Tarjan 边双连通分量 | 缩点 | LCA倍增优化 | 并查集

简介: 题目描述A network administrator manages a large network. The network consists of N computers and M links between pairs of computers. Any pair of computers are connected directly or indirectly by successive links, so data can be transformed between any two computers.

题目链接


题目描述


A network administrator manages a large network. The network consists of N computers and M links between pairs of computers. Any pair of computers are connected directly or indirectly by successive links, so data can be transformed between any two computers. The administrator finds that some links are vital to the network, because failure of any one of them can cause that data can’t be transformed between some computers. He call such a link a bridge. He is planning to add some new links one by one to eliminate all bridges.


You are to help the administrator by reporting the number of bridges in the network after each new link is added.


输入描述:


The input consists of multiple test cases. Each test case starts with a line containing two integers N( 1 ≤ N ≤ 100 , 000 ) and (N−1≤M≤200,000).

Each of the following M lines contains two integers A and B ( 1 ≤ A ≠ B ≤ N ), which indicates a link between computer A and B. Computers are numbered from 1 to N. It is guaranteed that any two computers are connected in the initial network.

The next line contains a single integer Q  ( 1 ≤ Q ≤ 1 , 000 ), which is the number of new links the administrator plans to add to the network one by one.


The i-th line of the following Q lines contains two integer A and B ( 1 ≤ A ≠ B ≤ N ) , which is the i-th added new link connecting computer A and B.

The last test case is followed by a line containing two zeros.


输出描述:


For each test case, print a line containing the test case number( beginning with 1) and Q lines, the i-th of which contains a integer indicating the number of bridges in the network after the first i new links are added. Print a blank line after the output for each test case.


示例1


输入

3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0


输出

Case 1:
1
0
Case 2:
2
0


首先纪念下这道题:

6d6ccc3782c8434f89f845e3e4a6b437.png


过题的过程比较艰辛(太菜了)


题意:


给出一个n个点n条边的图,有q次操作加询问,每次加入一条边,并询问加入这一条边之后的图中的桥的数量


思路:


偏向总结向,需要完美思路自行跳转下方

wa 30%的做法:

这道题目 中,遇到重边不可能是桥,所以说在建立边的时候,用map标记pair<int,int>,来维护那些便被标记,然后每次暴力跑 Tarjan [这里就注定了无法ac],统计桥的数量,如果说加入的边是被标记过的,那么说这条边就不再是桥,就将答案-1,看上去似乎是可行的,其实不对,👇

wa_code


tle 80%的做法:

在跑 Tarjan的过程中就应该在 low[to]>dfn[u]这个条件的基础上加入另一个条件:当前边是不是重边,重边肯定不是桥


所以这样就不用每次判断加入的边是不于是重边

这里过了大部分的数据,但是还是太年轻了,加入的边影响的可能不仅仅是小规模的图中的较为相邻的节点,有时候加入的边会影响很多个部分,而且只是适用于数据范围较小的情况,👇

tle_code


ac 100%的做法:

我们应该清楚地了解过一个叫做强连通分量的概念:

引[1] [https://blog.csdn.net/weixin_43843835/article/details/88381828]

如果一个有向图中,存在一条回路,所有的结点至少被经过一次,这样的图为强连通图。在强连通图的基础上加入一些点和路径,使得当前的图不在强连通,称原来的强连通的部分为强连通分量。

在这里我们求出边双连通分量:不存在割边的强连通分量成为边双连通分量


b6f32ae9513e4a3d95bb0f73a3d29f76.png


图片来源

因为图中存在桥,所以说我们直接求出边双连通分量并统计出数量在这里我们记作 cntSCC,开始的时候桥的数量是 cntSCC−1,并且桥的数量就是缩点之后边的数量。对于一个边双联通分量,我们将其看成一个点,那么这整张图就变成了一棵树,所以说就把图上的操作变成了树上的操作

对于每一次的操作,我们可以很清楚的了解到:


  1. 对于加入的一条边如果说这条边的两个端点在同一个边双联通分量里面,就不会对答案产生影响,那么说我们就不需要考虑对加入的这条边进行操作


  1. 如果说加入的这条边不在同一个边双联通分量里面,那么就可以说加入这条边之后,两点所在块之间的最短路径上的所有边都不再是桥,所以说就可以用 lca来进行操作,两个端点向祖先点跳,并标记沿途的边[考虑优化]

ac_code


#define lowbit(x) (x & (-x))
#define Clear(x,val) memset(x,val,sizeof x)
int n, m;
struct node {
  int to, nex;
} e[maxn << 2], e2[maxn << 2];
int cnt, head[maxn], dfc, hd[maxn], idx;
int dfn[maxn], low[maxn], pos[maxn];
int edge[maxn << 2];
int depth[maxn];
int cntSCC, ans;
void init() {
  idx = cnt = dfc = cntSCC = 0;
  for(int i = 1; i <= n; i++) {
    head[i] = -1;
    hd[i] = -1;
    pos[i] = 0;
    depth[i] = 0;
  }
//  memset(edge, 0, sizeof edge);/// set all bridge = 0
}
void add(int u, int v) {
  e[cnt].to = v;
  e[cnt].nex = head[u];
  head[u] = cnt ++;
}
void add2(int u,int v) {
  e2[idx].to = v;
  e2[idx].nex = hd[u];
  hd[u] = idx ++;
}
stack<int> stk;
void Tarjan(int u, int father) {
  stk.push(u);
  dfn[u] = low[u] = ++ dfc;
  for(int i = head[u]; ~i; i = e[i].nex) {
    int to = e[i].to;
    if(!dfn[to]) {
      Tarjan(to, u);
      low[u] = min(low[u], low[to]);
      if(low[to] > dfn[u]) edge[i] = edge[i ^ 1] = 1;/// is a brage
    } else if(to != father) {
      low[u] = min(low[u], dfn[to]);
    }
  }
  if(low[u] == dfn[u]) {
    pos[u] = ++ cntSCC;
    while(stk.top() != u) {
      pos[stk.top()] = cntSCC;
      stk.pop();
    }
    stk.pop();/// pop the point u
  }
}
int bz[maxn][20];
int fa[maxn];
void initfa() {
  for(int i=1; i<=n; i++) fa[i] = i;
}
int findfa(int x) {
  if(x == fa[x]) return x;
  else return fa[x] = findfa(fa[x]);
}
void _union(int u,int v) {
  int fau = findfa(u);
  int fav = findfa(v);
  if(fau == fav) return ;
  fa[fau] = fav;
}
void dfs(int u,int fat) {
  bz[u][0] = fat;
  depth[u] = depth[fat] + 1;
  for(int i=1; i<=19; i++) {
    bz[u][i] = bz[bz[u][i-1]][i-1];
  }
  for(int i=hd[u]; ~i; i=e2[i].nex) {
    int to = e2[i].to;
    if(to == fat) continue;
    dfs(to,u);
  }
}
int lca(int u,int v) {
  if(depth[u] < depth[v]) swap(u,v);
  for(int i=19; i>=0; i--) {
    if(depth[bz[u][i]] >= depth[v]) {
      u = bz[u][i];
    }
  }
  if(u == v) return v;
  for(int i=19; i>=0; i--) {
    if(bz[u][i] != bz[v][i]) {
      u = bz[u][i];
      v = bz[v][i];
    }
  }
  return bz[u][0];
}
int main() {
  int _ = 0;
  while(cin >> n >> m && (n || m)) {
    init();//
    memset(dfn,0,sizeof dfn);
    memset(low,0,sizeof low);
    memset(bz,0,sizeof bz);
    memset(edge,0,sizeof edge);
    for(int i = 1; i <= m; i++) {
      int u = read, v = read;
      add(u, v);
      add(v, u);
    }
    while(stk.size()) stk.pop();
    for(int i=1; i<=n; i++) {
      if(!dfn[i]) Tarjan(i, 0);
    }
//    puts("Tarjan _ ok");
    for(int i=1; i<=n; i++) {
      int u = i;
      for(int j=head[u]; ~j; j=e[j].nex) {
        int to = e[j].to;
        if(pos[i] == pos[to]) continue;
        add2(pos[i],pos[to]);
      }
    }
    dfs(1,0);///lca
    ans = cntSCC - 1;
    initfa();
    int q = read;
    printf("Case %d:\n", ++_);
    while(q --) {
      int u = read, v = read;
      if(pos[u] == pos[v]) printf("%d\n",ans);
      else {
        u = pos[u];
        v = pos[v];/// which scc are the u&&v in
        int _lca = lca(u,v);
        int fau = findfa(u);
        while(depth[_lca] < depth[fau]){
          fa[fau] = bz[fau][0];
          -- ans;
          fau = findfa(fau);
//          puts("bug???");
        }
        int fav = findfa(v);
        while(depth[_lca] < depth[fav]){
          fa[fav] = bz[fav][0];
          -- ans;
          fav = findfa(fav);
        }
        printf("%d\n",ans);
      }
    }
    puts("");
  }
  return 0;
}
/**
3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0
**/



目录
相关文章
|
2月前
|
人工智能 BI
【每日一题Day267】LC834树中距离之和 | 换根dp
【每日一题Day267】LC834树中距离之和 | 换根dp
29 0
|
9月前
华为机试HJ44:Sudoku(数独问题,深度优先遍历DFS解法)
华为机试HJ44:Sudoku(数独问题,深度优先遍历DFS解法)
|
11月前
|
存储 算法 数据建模
【最短路算法】SPFA
【最短路算法】SPFA
55 0
|
9月前
|
存储 容器
华为机试HJ41:称砝码(深度优先遍历dfs-Depth First Search)
华为机试HJ41:称砝码(深度优先遍历dfs-Depth First Search)
|
11月前
|
人工智能
POJ 2299 Ultra-QuickSort(树状数组+离散化+求逆序数)
POJ 2299 Ultra-QuickSort(树状数组+离散化+求逆序数)
51nod 1711 平均数(二分 + 树状数组好题)
51nod 1711 平均数(二分 + 树状数组好题)
82 0
|
人工智能 算法 BI
LeetCode 周赛 341 场,模拟 / 树上差分 / Tarjan 离线 LCA / DFS
上周末有单双周赛,双周赛我们讲过了,单周赛那天早上有事没参加,后面做了虚拟竞赛,然后整个人就不好了。前 3 题非常简单,但第 4 题有点东西啊,差点就放弃了。最后,被折磨了一个下午和一个大夜总算把第 4 题做出来了,除了新学的 Tarjon 离线算法,这道题还涉及到树上差分、前缀和、DFS、图论等基础知识,几度被折磨得想要放弃。这种感觉,似乎和当年在 LeetCode 上做前 10 题的时候差不多哈哈。
72 0
|
机器学习/深度学习 Java
【每日一题Day64】LC1799N 次操作后的最大分数和 | 状态压缩dp 状态压缩+dfs+记忆化搜索
每个数字有两种状态,删除或者未删除,因此可以使用状态压缩记录未删除的数字情况state,便于使用位运算进行状态的遍历及转移;使用状态压缩dp找到将所以数字删除的最大分数
87 0
【每日一题Day64】LC1799N 次操作后的最大分数和 | 状态压缩dp 状态压缩+dfs+记忆化搜索
Codeforces1153——D. Serval and Rooted Tree(思维好题+dfs+贪心)
Codeforces1153——D. Serval and Rooted Tree(思维好题+dfs+贪心)
105 0
Codeforces1153——D. Serval and Rooted Tree(思维好题+dfs+贪心)
UPC Graph (最小生成树 || 并查集+二分)
UPC Graph (最小生成树 || 并查集+二分)
77 0