POJ3694 Network —— 边双联通分量 + 缩点 + LCA + 并查集
题目链接:https://vjudge.net/problem/POJ-3694
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.
Input
The input consists of multiple test cases. Each test case starts with a line containing two integers N(1 ≤ N ≤ 100,000) and M(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.
Output
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.
Sample Input
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
Sample Output
Case 1:
1
0 Case 2:
2
0
题解:
1.利用Tarjan算法,求出每个边双联通分量,并且记录每个点属于哪一个分量。
2.将每一个边双联通分量缩成一个点,最终得到一棵树。而我们想要得到一棵有根树,怎么办?其实在执行Tarjan算法的时候,就已经形成了一个有根树。所以我们只需要在Tarjan算法的基础上,再记录每一个点的父节点以及深度就可以了。
3.每次询问的时候,如果两个点在同一个分量中,那么他们的相连不会减少桥的个数。如果两个点在不同的分量中,那么u->LCA(u,v)和v->LCA(u,v)上路径上的桥,都可以减少,路径上的点都可以缩成一个点,即合并成一个分量。
对于缩点的处理:
方法一:对于一个分量,可以设置一个点为实点,其余的点为虚点。实点即代表着这个分量的所有信息,虚点虽然属于这个分量的点,但是却对他视而不见。我们要做的,就是在这个分量里选择一个点,去代表整个分量。
方法二:同样地,我们也需要为每一个分量选出一个代表,以表示这个分量。与方法一的“视而不见”不同的是,方法二对每一个点都设置了一个归属集合,即表示这个点属于哪一个集合。由于在处理的过程中,一个集合可能又会被另一个集合所包含,所以我们可以利用并查集的路径压缩,很快地找到一个点的最终所属集合。
方法一:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
#define ms(a,b) memset((a),(b),sizeof((a)))
using namespace std;
typedef long long LL;
const double EPS = 1e-;
const int INF = 2e9;
const LL LNF = 2e18;
const int MAXN = 1e5+; struct Edge
{
int to, next;
}edge[MAXN*];
int tot, head[MAXN]; int index, dfn[MAXN], low[MAXN];
int isbridge[MAXN], sum_bridge;
int fa[MAXN], depth[MAXN]; void addedge(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
} void Tarjan(int u, int pre)
{
dfn[u] = low[u] = ++index;
depth[u] = depth[pre] + ; //记录深度
fa[u] = pre; //记录父亲结点
for(int i = head[u]; i!=-; i = edge[i].next)
{
int v = edge[i].to;
if(v==pre) continue;
if(!dfn[v])
{
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v]>dfn[u]) //isbridge[v]表示在树中,以v为儿子结点的边是否为桥
isbridge[v] = , sum_bridge++;
}
else
low[u] = min(low[u], dfn[v]);
}
} void LCA(int u, int v)
{
if(depth[u]<depth[v]) swap(u, v);
while(depth[u]>depth[v]) //深度大的先往上爬。遇到桥,就把它删去。
{
if(isbridge[u]) sum_bridge--, isbridge[u] = ;
u = fa[u];
}
while(u!=v) //当深度一样时,一起爬。遇到桥,就把它删去。
{
if(isbridge[u]) sum_bridge--, isbridge[u] = ;
u = fa[u];
if(isbridge[v]) sum_bridge--, isbridge[v] = ;
v = fa[v];
}
} void init()
{
tot = ;
memset(head, -, sizeof(head)); index = ;
memset(dfn, , sizeof(dfn));
memset(low, , sizeof(low));
memset(isbridge, , sizeof(isbridge)); sum_bridge = ;
} int main()
{
int n, m, kase = ;
while(scanf("%d%d", &n, &m) && (n||m) )
{
init();
for(int i = ; i<=m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v);
addedge(v, u);
} depth[] = ;
Tarjan(, );
int q, a, b;
scanf("%d", &q);
printf("Case %d:\n", ++kase);
while(q--)
{
scanf("%d%d", &a, &b);
LCA(a, b);
printf("%d\n", sum_bridge);
}
printf("\n");
}
}
方法二:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <map>
#include <string>
#include <set>
#define ms(a,b) memset((a),(b),sizeof((a)))
using namespace std;
typedef long long LL;
const double EPS = 1e-;
const int INF = 2e9;
const LL LNF = 2e18;
const int MAXN = 1e6+; struct Edge
{
int to, next;
}edge[MAXN], edge0[MAXN]; //edge为初始图, edge0为重建图
int tot, head[MAXN], tot0, head0[MAXN]; int index, dfn[MAXN], low[MAXN];
int top, Stack[MAXN], instack[MAXN];
int belong[MAXN];
int fa[MAXN], depth[MAXN]; //fa用于重建图时记录当前节点的父亲节点,depth记录当前节点的深度
int sum_bridge; //找到x最终所属的结合
int find(int x) { return belong[x]==x?x:belong[x]=find(belong[x]); } void addedge(int u, int v, Edge edge[], int head[], int &tot)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
} void Tarjan(int u, int pre)
{
dfn[u] = low[u] = ++index;
Stack[top++] = u;
instack[u] = true;
for(int i = head[u]; i!=-; i = edge[i].next)
{
int v = edge[i].to;
if(v==pre) continue;
if(!dfn[v])
{
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v]>dfn[u]) sum_bridge++;
}
else if(instack[v])
low[u] = min(low[u], dfn[v]);
} if(dfn[u]==low[u])
{
int v;
do
{
v = Stack[--top];
instack[v] = false;
belong[v] = u; //把集合的编号设为联通分量的第一个点
}while(v!=u);
}
} void build(int u, int pre)
{
fa[u] = pre; //记录父亲节点
depth[u] = depth[pre] + ; //记录深度
for(int i = head0[u]; i!=-; i=edge0[i].next)
if(edge0[i].to!=pre) //防止往回走
build(edge0[i].to, u);
} int LCA(int u, int v) //左一步右一步地找LCA
{
if(u==v) return u; //因为两个结点一定有LCA, 所以一定有u==v的时候 //可能爬一步就爬了几个深度,因为中间的结点已经往上缩点了
if(depth[u]<depth[v]) swap(u, v); //深度大的往上爬
sum_bridge--;
int lca = LCA(find(fa[u]), v);
return belong[u] = lca; //找到了LCA,在沿路返回的时候把当前节点的所属集合置为LCA的所属集合
} void init()
{
tot = tot0 = ;
memset(head, -, sizeof(head));
memset(head0, -, sizeof(head0)); index = top = ;
memset(dfn, , sizeof(dfn));
memset(low, , sizeof(low));
memset(instack, , sizeof(instack)); sum_bridge = ;
} int main()
{
int n, m, kase = ;
while(scanf("%d%d", &n, &m) && (n||m) )
{
init();
for(int i = ; i<=m; i++)
{
int u, v;
scanf("%d%d", &u, &v);
addedge(u, v, edge, head, tot);
addedge(v, u, edge, head, tot);
} Tarjan(, );
for(int u = ; u<=n; u++) //重建建图
for(int i = head[u]; i!=-; i = edge[i].next)
{
int tmpu = find(u);
int tmpv = find(edge[i].to);
if(tmpu!=tmpv)
addedge(tmpu, tmpv, edge0, head0, tot0);
} depth[find()] = ;
build(find(), find()); //把无根树转为有根树 int q, a, b;
scanf("%d", &q);
printf("Case %d:\n", ++kase);
while(q--)
{
scanf("%d%d", &a, &b);
LCA(find(a), find(b));
printf("%d\n", sum_bridge);
}
printf("\n");
}
}
POJ3694 Network —— 边双联通分量 + 缩点 + LCA + 并查集的更多相关文章
- POJ 3694Network(Tarjan边双联通分量 + 缩点 + LCA并查集维护)
[题意]: 有N个结点M条边的图,有Q次操作,每次操作在点x, y之间加一条边,加完E(x, y)后还有几个桥(割边),每次操作会累积,影响下一次操作. [思路]: 先用Tarjan求出一开始总的桥的 ...
- HDU5409---CRB and Graph 2015多校 双联通分量缩点
题意:一个联通的无向图, 对于每一条边, 若删除该边后存在两点不可达,则输出这两个点, 如果存在多个则输出第一个点尽可能大,第二个点尽可能小的. 不存在输出0 0 首先 若删除某一条边后存在多个联通分 ...
- POJ3177 Redundant Paths —— 边双联通分量 + 缩点
题目链接:http://poj.org/problem?id=3177 Redundant Paths Time Limit: 1000MS Memory Limit: 65536K Total ...
- POJ3694 Network 边双缩点+LCA+并查集
辣鸡错误:把dfs和ldfs搞混...QAQ 题意:给定一个无向图,然后查询q次,求每次查询就在图上增加一条边,求剩余割边的个数. 先把边双缩点,然后预处理出LCA的倍增数组: 然后加边时,从u往上跳 ...
- POJ 3694 (tarjan缩点+LCA+并查集)
好久没写过这么长的代码了,题解东哥讲了那么多,并查集优化还是很厉害的,赶快做做前几天碰到的相似的题. #include <iostream> #include <algorithm& ...
- 边双联通分量缩点+树的直径——cf1000E
题意理解了就很好做 题意:给一张无向图,任意取两个点s,t,s->t的路径上必经边数量为k 求这样的s,t,使得k最大 #include<bits/stdc++.h> #define ...
- Poj 3694 Network (连通图缩点+LCA+并查集)
题目链接: Poj 3694 Network 题目描述: 给出一个无向连通图,加入一系列边指定的后,问还剩下多少个桥? 解题思路: 先求出图的双连通分支,然后缩点重新建图,加入一个指定的边后,求出这条 ...
- 【UVA10972】RevolC FaeLoN (求边双联通分量)
题意: 给你一个无向图,要求把所有无向边改成有向边,并且添加最少的有向边,使得新的有向图强联通. 分析: 这题的解法还是很好想的.先用边双联通分量缩点,然后找新图中入度为0和为1的点,入度为0则ans ...
- 洛谷P2860 [USACO06JAN]冗余路径Redundant Paths(tarjan求边双联通分量)
题目描述 In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numbered 1. ...
随机推荐
- Vue编写轮播组件引入better-scroll插件无法正常循环轮播
临近过年还是发个博客表示一下自己的存在感,这段时间公司突然说想搞小程序,想到这无比巨大的坑就只能掩面而泣,于是乎这段时间在学习小程序开发.关于标题所说的是有老铁问的,我也跟着网上的代码码了一遍然后发现 ...
- css position是前端的你了解多少?
此文根据Steven Bradley的<How Well Do You Understand CSS Positioning?>所译,整个译文带有我自己的理解与思想,如果译得不好或不对之处 ...
- 三、Oracle常用内置函数
1. ASCII 返回与指定的字符对应的十进制数; SQL> select ascii(A) A,ascii(a) a,ascii(0) zero,ascii( ) space from d ...
- zoj 2886 Look and Say
Look and Say Time Limit: 2 Seconds Memory Limit: 65536 KB The look and say sequence is defined ...
- linux基础知识汇总
1.如何快速回到上次操作的目录? cd - 2.如何快速回到家目录? 直接cd或者cd ~ 3.怎么回到上一级目录? cd .. 4.什么是相对路径,什么是绝对路径? 相对路径就是相对于当前目录的位置 ...
- POJ 3177 边双连通求连通量度的问题
这道题的总体思路就是找到连通量让它能够看作一个集合,然后找这个集合的度,度数为1的连通量为k,那么需要添加(k+1)/2条边才可以保证边双连通 这里因为一个连通量中low[]大小是相同的,所以我们用a ...
- C++动态特性和C++对象模型——《高质量程序设计12章》
1.动态特性 静态特性和动态特性,编译时和运行时 虚函数 (1)虚函数的叫覆盖,虚函数不是实现多态的唯一手段(其他语言也可能采用别的方法). 抽象基类: (1)如果将基类的虚函数声明为纯虚函数,则基类 ...
- Mysql数据库的事物
一 .事物的特性:ACID 数据库的事务必须具备ACID特性,ACID是指 Atomicity(原子性).Consistensy(一致性).Isolation(隔离型)和Durability(持久性) ...
- Java实验--统计字母出现频率及其单词个数
本周的实验要求在之前实现统计单词的基础之上(可以见之前博客的统计单词的那个实验),对其进行修改成所需要的格式,统计字母出现频率的功能,并按照一定的格式把最终结果的用特定的格式在文本中显示出来 统计过程 ...
- java基础 4 继承(3)this 与 super关键字
this用来指向当前实例对象,用来区别成员变量与方法的形参 super可以用来访问父类的方法或成员变量,当子类构造函数需要显示的调用父类的构造函数时,super()必须为构造函数中的第一条语句.