tarjan算法与无向图的连通性(割点,桥,双连通分量,缩点)
基本概念
给定无向连通图G = (V, E)
割点:
对于x∈V,从图中删去节点x以及所有与x关联的边之后,G分裂为两个或两个以上不相连的子图,则称x为割点
割边(桥)
若对于e∈E,从图中删去边e之后,G分裂成两个不相连的子图,则称e为G的桥或割边
时间戳
在图的深度优先遍历过程中,按照每个节点第一次被访问的时间顺序,依次给予N个节点1~N的整数标记,该标记被称为“时间戳”,记为dfn[x]
搜索树
在无向连通图中任选一个节点出发进行深度优先遍历吗,每个节点只访问一次。所有发生递归的边(x, y)构成一棵树,称为“无向连通图的搜索森林”。一般无向图的各个连通块的搜索树构成无向图的“搜索森林”。对于深度优先遍历出的搜索树,按照被遍历的次序,标记节点的时间戳
追溯值
追溯值low[x]。设subtree(x)表示搜索树中以x为根的子树。low[x]定义为以下节点时间戳的最小值
low[u]定义为u或者u的子树中能够通过非父子边(父子边就是搜索树上的边)追溯到的最早的节点的时间戳
即:
1.subtree(x)中的节点
2.通过一条不在搜索树上的边,能够到达subtree(x)的节点
为了计算low[x],应该先令low[x] = dfn[x],然后考虑从x出发的每条边(x, y):
若在搜索树上x是y的父节点,则令low[x] = min(low[x], low[y])
若无向边(x, y)不是搜索树上的边,则令low[x] = min(low[x], dfn[y])
桥的判定法则
无向边(x, y)是桥,当且仅当搜索树上存在x的一个子节点y,满足:
dfn[x] < low[y]
根据定义,dfn[x] <low[y]说明从subtree(y)出发,在不经过(x, y)的前提下,不管走哪条边,都无法到达x或比x更早访问的节点。若把(x, y)删除,则subtree(y)就好像形成了封闭的环境,与节点x没有边相连,图断成了两部分,(x, y)为桥
反之,若不存在这样的子节点x和y,使得dfn[x] < low[y],这说明每个subtree(y)都能绕行其他边到x或比x更早的节点,(x, y)也就不是桥
桥一定是搜索树中的边,并且一个简单环中的边一定都不是桥
需要注意的是, 因为我们要遍历的是无向图, 所以从每个节点x出发,总能访问到他的父节点fa,根据low的计算方法,(x, fa)属于搜索树上的边,且fa不是x的子节点,故不能用fa的时间戳来更新low[x]。
如果仅记录每个节点的父节点,会无法处理重边的情况——当x与fa之间有多条边时,(x, fa)一定不是桥,在这些重复计算中,只有一条边在搜索树上,其他的几条都不算,故有重边时,dfn[fa]不能用来更新low[x]
解决方案是:记录“递归进入每个节点的边的编号”。编号可认为是边在邻接表中储存下标位置。把无向图的每条边看做双向边,成对存储在下标"2和3","4和5","6和7"...处。若沿着编号i的边递归进入节点x,则忽略从x出发的编号为i xor 1的边,通过其他边计算low[x]即可
(补充:^的成对变换
对于非负整数n
当n为偶数时,n xor 1等于n+1
当n为奇数时,n xor 1等于n-1
因此“0与1”“2与3”“3与5”……关于xor 1运算构成了成对变换
这一性质经常用于图论邻接表中边集的存储。在具有无向边(双向边)的图中把一对正反方向的边分别储存在邻接表数组的第n与n+1个位置(其中n为偶数),就可以通过xor 1运算获得与当前边(x, y)反向的边(y, x)的存储位置
在程序开始时,初始化变量tot = 1。这样每条无向边看成的两条有向边会成对存储在ver和edge数组的下表“2和3”“4和5”“6和7”……的位置上。通过对下表xor 1操作,就可以直接定位到与当前反向的边。换句话说,如果ver[i]是第i条边的终点,那么ver[i ^ 1]就是第i条边的起点)
#include<bits/stdc++.h>
using namespace std;
const int maxn = ;
struct node {
int y, net;
}e[maxn << ];
int lin[maxn], len = ;
bool bridge[maxn << ];
int dfn[maxn], low[maxn];
int n, m, num; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} inline void insert(int xx, int yy) {
e[++len].net = lin[xx];
e[len].y = yy;
lin[xx] = len;
} inline void tarjan(int x, int in_edge) {
dfn[x] = low[x] = ++num;
for(int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(!dfn[to]) {
tarjan(to, i);
low[x] = min(low[x], low[to]);
if(low[to] > dfn[x])
bridge[i] = bridge[i ^ ] = true;
}
else if(i != (in_edge ^ ))
low[x] = min(low[x], dfn[to]);
}
} int main() {
n = read(), m = read();
len = ;
for(int i = ; i <= m; ++i) {
int x, y, z;
x = read(), y = read();
insert(x, y);
insert(y, x);
}
for(int i = ; i <= n; ++i)
if(!dfn[i]) tarjan(i, );
for(int i = ; i < len; i += )
if(bridge[i])
cout << e[i ^ ].y << ' ' << e[i].y << '\n';
return ;
}
求桥的板子(参考即可,细节错误请无视)
割点的判定法则
割点的判定法则
若x不是搜索树的根节点,则x是割点当且仅当搜索树上存在x的一个子节点y,满足:
dfn[x] <= low[y]
特别地,若x是搜索树的根节点,则x是割点当且仅当搜索树上存在至少两个子节点y1, y2满足上述条件
割点判定的符号为小于等于号,不必再考虑父节点和重边的问题
#include<bits/stdc++.h>
using namespace std;
const int maxn = ;
struct node {
int y, net;
}e[maxn << ];
int lin[maxn], len = ;
int n, m, root, num = ;
int dfn[maxn], low[maxn];
bool cut[maxn]; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} inline void insert(int xx, int yy) {
e[++len].y = yy;
e[len].net = lin[xx];
lin[xx] = len;
} void tarjan(int x) {
dfn[x] = low[x] = ++num;
int flag = ;
for(int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(!dfn[to]) {
tarjan(to);
low[x] = min(low[x], low[to]);
if(low[to] >= dfn[x]) {
flag++;
if(x != root || flag > ) cut[x] = true;
}
}
else low[x] = min(low[x], dfn[to]);
}
} int main() {
n = read(), m = read();
len = ;
for(int i = ; i <= m; ++i) {
int x, y;
x = read(), y = read();
if(x == y) continue;
insert(x, y);
insert(y, x);
}
for(int i = ; i <= n; ++i)
if(!dfn[i]) {
root = i;
tarjan(i);
}
for(int i = ; i <= n; ++i)
if(cut[i])
cout << i << ' ';
cout << "are cut-vertexes" << '\n';
return ;
}
割点板子
双连通分量
若一张无向图不存在割点,则称它为点双联通图。
无向图的“极大点双连通子图”称为“点双连通分量”,简记为“v-BCC”。无向连通图的极大边双连通图的极大边双连通子图,称为“边双连通分量”,简记为“e-DCC”
统称为“双连通分量”,简记为“BCC”
定理:
一张无向连通图是“点双连通图”,当且仅当满足下列两个条件之一:
1.图的顶点不超过2
2.图中任意两点都同时包含在至少一个简单环中。其中,简单环指的是不自交的环。
一张无向连通图是“边双连通图”,当且仅当任意一条边都包含在至少一个简单环中。
边双连通分量的求法
求出无向图中所有的桥,将桥删除后,图会分成若干块,每一个连通块都是一个“边双连通分量”
先用tarjan算法标记出所有的桥。然后再对整个无向图执行一次深度优先遍历(遍历的过程中不访问),划分出每个连通块。
边双连通分量缩点
把每个e-BCC看作一个节点,把桥边(x, y)看做连接编号为c[x]和c[y]的e-BCC对应节点的无向边,会产生一棵树(若原来的无向图不连通,则产生森林)。
这种把e-BCC收缩为一个节点的方法称为“缩点”。
把e-BCC缩点,存储在另一个邻接表中
#include<bits/stdc++.h>
using namespace std;
const int maxn = ;
struct shiki {
int y, net;
}e[maxn << ], e_BCC[maxn << ];
int n, m, num = ;
int lin[maxn], len = ;
int dfn[maxn], low[maxn];
bool bridge[maxn << ];
int c_id[maxn], v_bcc = ;
int c_lin[maxn], c_len = ; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} inline void insert(int xx, int yy) {
e[++len].y = yy;
e[len].net = lin[xx];
lin[xx] = len;
} inline void t_bridge(int x, int in_edge) {
dfn[x] = low[x] = ++num;
for(register int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(!dfn[to]) {
t_bridge(to, i);
low[x] = min(low[x], low[to]);
if(dfn[x] < low[to])
bridge[i] = bridge[i ^ ] = ;
}
else if(i != (in_edge ^ ))
low[x] = min(low[x], dfn[to]);
}
} void dfs(int x) {
c_id[x] = v_bcc;
for(register int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(c_id[to] || bridge[i]) continue;
dfs(to);
}
} inline void add_c(int xx, int yy) {
e_BCC[++c_len].y = yy;
e_BCC[c_len].net = c_lin[xx];
c_lin[xx] = c_len;
} int main() {
memset(dfn, , sizeof(dfn));
n = read(), m = read();
len = ;
for(register int i = ; i <= m; ++i) {
int x, y;
x = read(), y = read();
insert(x, y);
insert(y, x);
}
for(register int i = ; i <= n; ++i)
if(!dfn[i]) t_bridge(i, );
for(register int i = ; i < len; i += )
if(bridge[i]) cout << e[i ^ ].y <<' ' << e[i].y << '\n';
for(register int i = ; i <= n; ++i)
if(!c_id[i]) {
++v_bcc;
dfs(i);
}
for(register int i = ; i <= n; ++i)
cout << i << ' ' << "belong to" << ' ' << c_id[i] << '\n';
for(register int i = ; i <= len; ++i) {
int x = e[i ^ ].y, y = e[i].y;
if(c_id[x] == c_id[y]) continue;
add_c(c_id[x], c_id[y]);
// add_c(c_id[y], c_id[x]);
}
printf("缩点后的森林, 点数%d, 边数%d(可能有重边)\n", v_bcc, c_len / );
for(register int i = ; i <= c_len; ++i)
cout << e_BCC[i ^ ].y << ' ' << e_BCC[i].y << '\n';
return ;
}
桥与边双与缩点
点双连通分量求法
若某个节点是孤立点,则它自己构成一个v-BCC。除了孤立点外,点双连通分量的大小至少为2.根据v-DCC定义的极大性,虽然桥不属于任何e-DCC,但是割点可能属于多个v-DCC
为了求出“点双连通分量”,需要在atjan算法的过程中维护一个栈,并按照如下方式维护栈中元素:
1.当一个节点第一次被访问时,把该节点入栈
2.当割点判定法则中的条件dfn[x] <= low[y]成立时,无论x是否为根,都需要:
(1)从栈顶不断弹出节点,直至节点y被弹出
(2)刚才弹出的所有节点与节点x一起构成一个v-BCC
点双连通分量缩点
因为一个割点可能属于多个v-BCC,设图中有p个割点和t个v-BCC,我们建立一张包含p+t个节点的新图,把每个v-BCC和每个割点都作为新图中的节点,并在每个割点与包含它的所有v-BCC之间连边。
这张图就变成了一棵树或是一片森林
//尚缺缩点
#include<bits/stdc++.h>
#define uint unsigned int
using namespace std;
const int maxn = ;
struct shiki {
int y, net;
}e[maxn << ], vbcc[maxn];
int n, m, root, num = ;
int lin[maxn], len = , cnt = ;
int dfn[maxn], low[maxn];
bool cut[maxn];
int s[maxn], top = ;
int new_id[maxn];
int v_lin[maxn], v_len = ;
vector<int> dcc[maxn];
int c[maxn]; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} inline void insert(int xx, int yy) {
e[++len].y = yy;
e[len].net = lin[xx];
lin[xx] = len;
} void t_vertexes(int x) {
dfn[x] = low[x] = ++num;
s[++top] = x;
if(x == root && lin[x] == ) {
dcc[++cnt].push_back(x);
return;
}
int flag = ;
for(register int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(!dfn[to]) {
t_vertexes(to);
low[x] = min(low[x], low[to]);
if(dfn[x] <= low[to]) {
flag++;
if(x != root || flag > ) cut[x] = ;
cnt++;
int z;
do {
z = s[top--];
dcc[cnt].push_back(z);
}while(z != to);
dcc[cnt].push_back(x);
}
}
else low[x] = min(low[x], dfn[to]);
}
} inline void add_c(int xx, int yy) {
vbcc[++v_len].y = yy;
vbcc[v_len].net = v_lin[xx];
v_lin[xx] = v_len;
} int main() {
n = read(), m = read();
for(register uint i = ; i <= m; ++i) {
int x, y;
x = read(), y = read();
insert(x, y);
insert(y, x);
}
for(register uint i = ; i <= n; ++i)
if(!dfn[i]) {
root = i;
t_vertexes(i);
}
for(register uint i = ; i <= n; ++i)
if(cut[i]) cout << i << ' ';
cout << "are cut-vertexes" << '\n';
for(int i = ; i <= cnt; ++i) {
printf("e-DCC #%d:", i);
for(int j = ; j < dcc[i].size(); ++j)
printf(" %d", dcc[i][j]);
cout << '\n';
}
num = cnt;
for(register int i = ; i <= n; ++i)
if(cut[i]) new_id[i] = ++num;//给割点以新的编号
v_len = ;
for(register int i = ; i <= cnt; ++i)
for(register int j = ; j < dcc[i].size(); ++j) {
int x = dcc[i][j];
if(cut[x]) {
add_c(i, new_id[x]);
add_c(new_id[x], i);
}
else c[x] = i;//除割点外,其它点仅属于1个v-bcc
}
printf("缩点之后的森林, 点数%d, 边数%d\n", num, v_len / );
printf("编号1~%d的为原图v-BCC, 编号>%d的原图割点\n", cnt, cnt);
for(register int i = ; i < v_len; i += )
printf("%d %d\n", vbcc[i ^ ], vbcc[i]);
return ;
}
割点与点双与缩点
例题1:BLO(Bzoj1123)
对于询问的节点i,可能有两种情况
1.i不是割点
2.i是割点
若i不是割点,则将i以及与i有直接相连的边删去后,图分为了i和其他节点这个两部分
即:i被孤立出来了
此时对于不能与i联通的点的个数为n-1,即有n-1个点不能与i相互到达。
因为题目求的是有序点对,也就是说,例如(1, 2)和(2, 1),这是不同的点对。
所以若i不是割点,则答案为2*(n-1)
若i是割点,则删去i以及所有与i相连的边后,图会分成若干个连通块。
最后的答案很显然,我们应该求出这些连通块的大小,两两相乘再相加
在图的连通性问题中,我们经常要从搜索树的角度来进行分析。
设在搜索树上,节点i的子节点集合中,有t割点s1,s2,s3...st满足割点判定法则dfn[i] <= low[sk]。于是删除i关联的所有边后无向图至多分成t+2个连通块
每个连通块的节点构成情况为:
1.节点i自身单独构成一个连通块
2.有t个连通块,分别由搜索树上以sk(1 <= k <= t)为根的子树中的节点构成
3.还可能有一个连通块,由除了上述节点以外的所有点构成。
(第三点,即虽然与i相连通,但i不作为搜索树的根。因为整个图是连通的,在不删掉任何一个点是,搜索树只有一个点为根,删掉与i直接相连的边,则被分开的是i,
i的子树和i的父亲所在了连通块)
那么可以在tarjan算法执行深度优先遍历的过程正,顺便求出搜索树每棵“子树”的大小,设size[x]表示已x为根的子树的大小。
综上所述,删掉一个割点i之后,不连通的有序对数量为:
设sum = size[s1]+size[s2]+....+size[t-1]+size[t]
size[s1]*(n-size[s1])+size[s2]*(n-size[s2])+...+size[st]*(n-size[st])+1*(n-1)+(n-1-sum)*(1+sum)
#include<iostream>
#include<iomanip>
#include<ctime>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>
#include<stack>
#define ll long long
#define uint unsigned int
using namespace std;
const int maxn = , maxm = ;
struct shiki {
int y, net;
}e[maxm << ];
int lin[maxn], len = ;
int n, m, num = ;
int size[maxn];
ll ans[maxn];
int dfn[maxn], low[maxn];
bool cut[maxn]; inline int read() {
int x = , y = ;
char ch = getchar();
while(!isdigit(ch)) {
if(ch == '-') y = -;
ch = getchar();
}
while(isdigit(ch)) {
x = (x << ) + (x << ) + ch - '';
ch = getchar();
}
return x * y;
} inline void insert(int xx, int yy) {
e[++len].y = yy;
e[len].net = lin[xx];
lin[xx] = len;
} void tarjan(int x) {
dfn[x] = low[x] = ++num, size[x] = ;
int flag = , sum = ;
for(register int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(!dfn[to]) {
tarjan(to);
size[x] += size[to];
low[x] = min(low[x], low[to]);
if(dfn[x] <= low[to]) {
flag++;
ans[x] += 1ll * size[to] * (n - size[to]);
sum += size[to];
if(x != || flag > ) cut[x] = ;
}
}
else low[x] = min(low[x], dfn[to]);
}
if(cut[x]) ans[x] += 1ll * (n - sum - ) * (sum + ) + (n - );
else ans[x] = * (n - );
} int main() {
n = read(), m = read();
for(register int i = ; i <= m; ++i) {
int x, y;
x = read(), y = read();
if(x == y) continue;
insert(x, y);
insert(y, x);
}
tarjan();//因为已经确保图是连通的 ,所以可以直接计算
for(register int i = ; i <= n; ++i)
printf("%lld\n", ans[i]);
return ;
}
例题2:Network(poj3694)
给定一张N个点M条边的无向连通图,然后执行Q次操作,每次想图中添加一条边,并且询问当前无向图中“桥”的数量(N <= 10^5, m <= 2*10^5, Q<=1000)
先利用tarjan算法求出无向图中所有的边双连通分量,并对所有的边双连通分量执行缩点操作。
这样就形成了一颗树,这个树上的边就是最初“桥”的个数
思考加入新的边(x, y)
对于x和y两个点
1.若两个点在同一个e-BCC中,则在x和y之间连边不会影响最终桥的数量
2.若x,y属于不同的e-BCC,则在缩点之后得到的树上,x所在的边双和y所在的边双之间的路径都不在是桥,因为将x,y连边后,他们都处在一个环中。
我们可以求出P = LCA(c_id[x], c_id[y]),即:x所在的边双与y所在的边双的最近公共祖先,同时把路径上的所有边都标记为“不是桥”。
因为数据不算大,所以我们就可以AC了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstdlib>
#include<ctime>
#include<iomanip>
#include<cmath>
using namespace std;
const int maxm = , maxn = ;
struct shiki {
int y, net;
}e[maxm];
int low[maxn], dfn[maxn], num = , deep[maxn];
int len, n, m, lin[maxn];
int flag, bridge[maxn], father[maxn]; inline void init() {
len = , num = , flag = ;
memset(dfn, , sizeof(dfn));
memset(deep, , sizeof(deep));
memset(bridge, , sizeof(bridge));
memset(lin, , sizeof(lin));
memset(low, , sizeof(low));
memset(father, , sizeof(father));
for(int i = ; i <= n; i++) father[i] = i;
} inline void insert(int xx, int yy) {
e[++len].y = yy;
e[len].net = lin[xx];
lin[xx] = len;
} void tarjan(int x) {
dfn[x] = low[x] = ++num;
deep[x] = deep[father[x]] + ;
for(int i = lin[x]; i; i = e[i].net) {
int to = e[i].y;
if(!dfn[to]) {
father[to] = x;
tarjan(to);
low[x] = min(low[x], low[to]);
if(low[to] > dfn[x]) {
flag++;
bridge[to] = ;
}
}
else if(to != father[x]) low[x] = min(low[x], dfn[to]);
} } void LCA(int u, int v) {
while(deep[u] > deep[v]) {
if(bridge[u]) flag--, bridge[u] = ;
u = father[u];
}
while(deep[v] > deep[u]) {
if(bridge[v]) flag--, bridge[v] = ;
v = father[v];
}
while(u != v) {
if(bridge[u]) flag--, bridge[u] = ;
if(bridge[v]) flag--, bridge[v] = ;
u = father[u];
v = father[v];
}
}
void ask()
{
int q, u, v;
scanf("%d", &q);
for(int i = ; i <= q; ++i) {
scanf("%d%d", &u, &v);
LCA(u, v);
printf("%d\n", flag);
}
printf("\n");
}
int main()
{
int cas = ;
while(scanf("%d%d", &n, &m) != EOF) {
if(n == && m == ) break;
printf("Case %d:\n", ++cas);
init();
int x, y;
while(m--) {
scanf("%d%d", &x, &y);
insert(x, y);
insert(y, x);
}
tarjan();
ask();
}
return ;
}
tarjan算法与无向图的连通性(割点,桥,双连通分量,缩点)的更多相关文章
- tarjan算法应用 割点 桥 双连通分量
tarjan算法的应用. 还需多练习--.遇上题目还是容易傻住 对于tarjan算法中使用到的Dfn和Low数组. low[u]:=min(low[u],dfn[v])--(u,v)为后向边,v不是u ...
- [Tarjan系列] Tarjan算法求无向图的双连通分量
这篇介绍如何用Tarjan算法求Double Connected Component,即双连通分量. 双联通分量包括点双连通分量v-DCC和边连通分量e-DCC. 若一张无向连通图不存在割点,则称它为 ...
- tarjan算法求无向图的桥、边双连通分量并缩点
// tarjan算法求无向图的桥.边双连通分量并缩点 #include<iostream> #include<cstdio> #include<cstring> ...
- tarjan算法--求无向图的割点和桥
一.基本概念 1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 2.割点:无向连通图中 ...
- tarjan算法--求解无向图的割点和桥
1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥 也就是说 无向连通图中,如果删除某边后,图变成不连通,则称该边为桥 2.割点:无向连通图中,如 ...
- [Tarjan系列] Tarjan算法求无向图的桥和割点
RobertTarjan真的是一个传说级的大人物. 他发明的LCT,SplayTree这些数据结构真的给我带来了诸多便利,各种动态图论题都可以用LCT解决. 而且,Tarjan并不只发明了LCT,他对 ...
- Tarjan算法:求解图的割点与桥(割边)
简介: 割边和割点的定义仅限于无向图中.我们可以通过定义以蛮力方式求解出无向图的所有割点和割边,但这样的求解方式效率低.Tarjan提出了一种快速求解的方式,通过一次DFS就求解出图中所有的割点和割边 ...
- Light OJ - 1026 - Critical Links(图论-Tarjan算法求无向图的桥数) - 带详细注释
原题链接 无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 也可以先用Tajan()进行dfs算出所有点 的low和dfn值,并记录dfs过程中每个 点的父节点:然后再把所有点遍历一遍 ...
- 图论-桥/割点/双连通分量/缩点/LCA
基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个 ...
随机推荐
- [洛谷P3254]圆桌问题
题目大意:有$m$个单位,每个单位有$r_i$个代表,有$n$张餐桌,每张餐桌可容纳$c_i$个代表.要求同一个单位的代表不在同一个餐桌就餐.若可以,输出$1$以及其中一种方案,否则输出$0$ 题解: ...
- BZOJ3289 Mato的文件管理 【莫队 + 树状数组】
3289: Mato的文件管理 Time Limit: 40 Sec Memory Limit: 128 MB Submit: 3964 Solved: 1613 [Submit][Status] ...
- js金额转大写数字
//金额转大写数字 const intToChinese = money => { //汉字的数字 let cnNums = new Array('零', '壹', '贰', '叁', '肆', ...
- How to turn off the binary log for mysqld_multi instances?
Q: MySQL supports running multiple mysqld on the same server. One of the ways is to use mysqld_multi ...
- HDU 5655 四边形判断
CA Loves Stick Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others) ...
- 深入探索 高效的Java异常处理框架
转载自:http://www.sunwei.org/archives/196 摘要:本文从Java异常最基本的概念.语法开始讲述了Java异常处理的基本知识,分析了Java异常体系结构,对比Sprin ...
- windows远程桌面访问ubuntu12.04
转载自 : http://blog.csdn.net/shuzui1985/article/details/7592569 1.dashboard----桌面共享 我们共享所使用的协议是rdp,所以我 ...
- c++ 公有继承的赋值兼容规则
赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代.通过公有继承,派生类得到了基类中除构造函数.析构函数之外的所有成员,而且所有成员的访问控制属性也和基类完全相同.这样,公有派生 ...
- 关于fragment点击能穿透问题
本人在做项目的过程中遇到的这个问题,然后就在网上百度了一下,之后也是在csdn上看到博友发过此类问题的解决办法,所以特此重新总结一下,顺便也给自己提个醒,避免出现此类问题.好!下面我们说一下问题: 举 ...
- POJ 1320 Street Numbers 解佩尔方程
传送门 Street Numbers Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 2529 Accepted: 140 ...