Network
LCA+tarjan+桥+dsu
蒟蒻刷到个难题。。。。。。
题目给的图一定是联通的。
我们大致的想法是:先进行缩点。把图变成一个树。节点与节点之间用桥连接。
这并不难,我们tarjan一下,找到桥然后染色就可以了。
然后添加边时。在书上的两个节点之间连一条边。一定会构成一个环。
这个环中原本的边会失去桥的属性。
那么:假设在u和v之间建边。u,v的最近公共祖先为LCA。
那么u->LCA v->LCA 之间的边都不在是桥。
我刚开始的代码是从u和v暴力向上找公共祖先。并一路削除路过的边的桥的属性。
如果,已经是消除过的状态无妨。如果是第一次消除,那么桥的数量就减一。
#include<iostream>
#include<algorithm>
using namespace std;
const int max_n = 1e5 + 100;
const int max_m = 2e5 + 100;
struct edge{
int to, rev, next;
bool vis;
}E[max_m << 1];
int head[max_n];
int cnt = 1;
void add(int from, int to, int rev, int vis = true) {
E[cnt].to = to;E[cnt].vis = vis;
E[cnt].next = head[from];E[cnt].rev = rev;
head[from] = cnt++;
}
int dfn[max_n], low[max_n];
int tot = 0;
void tarjan(int u,int fa_id) {
dfn[u] = low[u] = ++tot;
for (int i = head[u];i;i = E[i].next) {
int v = E[i].to;
if (i == fa_id)continue;
if (dfn[v] == -1) {
tarjan(v, E[i].rev);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u])E[i].vis = E[E[i].rev].vis = false;
}else low[u] = min(low[u], dfn[v]);
}
}
int fa[max_n];
int cl[max_n];
int cls = 0;
int dep[max_n];
void dfs(int u, int color, int p) {
fa[color] = p;cl[u] = color;dep[color] = dep[p] + 1;
for (int i = head[u];i;i = E[i].next) {
edge& e = E[i];
if (cl[e.to] != 0)continue;
if (e.vis == false)dfs(e.to, ++cls, color);
else dfs(e.to, color, p);
}
}
bool already[max_n];
int LCA(int u,int v) {
int res = 0;
u = cl[u];v = cl[v];
if (dep[u] < dep[v])swap(u, v);
while (dep[u] > dep[v]) {
if (!already[u])++res;
already[u] = true;
u = fa[u];
}while (u != v) {
if (!already[u])++res;
if (!already[v])++res;
already[u] = already[v] = true;
u = fa[u];v = fa[v];
}return res;
}
int N, M, Q;
int main() {
ios::sync_with_stdio(0);
int tcase = 0;
while (cin >> N >> M) {
if (N == 0 && M == 0)break;
fill(head, head + max_n, 0);cnt = 1;
fill(dfn, dfn + max_n, -1);tot = 0;
fill(already, already + max_n, false);
fill(cl, cl + max_n, 0);cls = 0;
cout << "Case " << ++tcase << ":\n";
for (int i = 1, u, v;i <= M;++i) {
cin >> u >> v;
add(u, v, cnt + 1);
add(v, u, cnt - 1);
}tarjan(1, -1);
dfs(1, ++cls, 0);
int ans = 0;
for (int i = 1;i < cnt;++i)
if (!E[i].vis)
++ans;
ans >>= 1;
cin >> Q;
for (int i = 1, u, v;i <= Q;++i) {
cin >> u >> v;
ans -= LCA(u, v);
cout << ans << endl;
}cout << endl;
}
}但,仔细想想。这种做法复杂度很高。
因为,万一我们的树退化成链表。然后Q次查询全部都是首和尾。
此时最坏的情况将是O(n*q)看下数据10^8。。。被放过了。
如何优化这个算法。利用并查集,也就是说,我们在树上也进行缩点。
将u->LCA v->LCA一路上的点都并入LCA.
同时,在找LCA时代码也有所不同了。很是巧妙。
#include<iostream>
#include<algorithm>
using namespace std;
const int max_n = 1e5 + 100;
const int max_m = 2e5 + 100;
struct edge{
int to, rev, next;
bool vis;
}E[max_m << 1];
int head[max_n];
int cnt = 1;
void add(int from, int to, int rev, int vis = true) {
E[cnt].to = to;E[cnt].vis = vis;
E[cnt].next = head[from];E[cnt].rev = rev;
head[from] = cnt++;
}
int dfn[max_n], low[max_n];
int tot = 0;
void tarjan(int u,int fa_id) {
dfn[u] = low[u] = ++tot;
for (int i = head[u];i;i = E[i].next) {
int v = E[i].to;
if (i == fa_id)continue;
if (dfn[v] == -1) {
tarjan(v, E[i].rev);
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u])E[i].vis = E[E[i].rev].vis = false;
}else low[u] = min(low[u], dfn[v]);
}
}
int fa[max_n];
int cl[max_n];
int cls = 0;
int dep[max_n];
void dfs(int u, int color, int p) {
fa[color] = p;cl[u] = color;dep[color] = dep[p] + 1;
for (int i = head[u];i;i = E[i].next) {
edge& e = E[i];
if (cl[e.to] != 0)continue;
if (e.vis == false)dfs(e.to, ++cls, color);
else dfs(e.to, color, p);
}
}
int par[max_n];
int find(int x) { return x == par[x] ? x : par[x] = find(par[x]); }
int LCA(int u,int v) {
int res = 0;
u = cl[u];v = cl[v];
int uu = find(u);int vv = find(v);
while (uu != vv) {
if (dep[uu] > dep[vv]) {
par[uu] = find(fa[uu]);
uu = find(fa[uu]);
++res;
}
else if (dep[vv] > dep[uu]) {
par[vv] = find(fa[vv]);
vv = find(fa[vv]);
++res;
}
else {
par[uu] = find(fa[uu]);
uu = find(fa[uu]);
par[vv] = find(fa[vv]);
vv = find(fa[vv]);
++res;++res;
}
}return res;
}
int N, M, Q;
int main() {
ios::sync_with_stdio(0);
int tcase = 0;
while (cin >> N >> M) {
if (N == 0 && M == 0)break;
for (int i = 0;i < max_n;++i)par[i] = i;
fill(head, head + max_n, 0);cnt = 1;
fill(dfn, dfn + max_n, -1);tot = 0;
fill(cl, cl + max_n, 0);cls = 0;
cout << "Case " << ++tcase << ":\n";
for (int i = 1, u, v;i <= M;++i) {
cin >> u >> v;
add(u, v, cnt + 1);
add(v, u, cnt - 1);
}tarjan(1, -1);
dfs(1, ++cls, 0);
int ans = 0;
for (int i = 1;i < cnt;++i)
if (!E[i].vis)
++ans;
ans >>= 1;
cin >> Q;
for (int i = 1, u, v;i <= Q;++i) {
cin >> u >> v;
ans -= LCA(u, v);
cout << ans << endl;
}cout << endl;
}
}kuangbin刷题题单详解(连通图) 文章被收录于专栏
题单:https://vjudge.net/article/371
深信服公司福利 804人发布