Network(poj 3694)
原题链接
题目类型:缩点+边双连通分量
思路一:
存在的问题:
1.可能存在重边,又无向图中要求子节点不能通过反边访问父亲节点,因此需要将边的序号作为条件传入参数中。
2.加入了一条新的边后如何求得割边的个数,dfs遍历到目的节点,然后列出路径上的割边的个数,并在dfs的过程中做出标记标记为不是割边。需要注意的是,需要设置dfs的返回值,以标志该条路径是否为到达目的块的路径。
3.那么应该遍历的是缩点后的图还是原先的图呢?由于本题时间限制较少,因此可以直接在原图上进行遍历。
具体的实现代码如下所示:
//WA代码
#include<iostream>
#include<vector>
#include<cstring>
#include<stack>
#define NMAX 100005
using namespace std;
int F, R;
struct edge {
int from, to, next;
int is;//是否为割边
};
vector<edge> E;
int h[NMAX];
int a, b;
void addline(int x, int y) {
edge cur;
cur.from = x, cur.to = y, cur.next = h[x], cur.is = 0;
h[x] = E.size();
E.push_back(cur);
cur.from = y, cur.to = x, cur.next = h[y], cur.is = 0;
h[y] = E.size();
E.push_back(cur);
}
int dfn[NMAX], low[NMAX], belong[NMAX];
stack<int > S;
int tot = 0;
int connect = 0;
void tarjan(int x, int fa, int side) {
dfn[x] = low[x] = ++tot;
S.push(x);
for (int i = h[x]; i != -1; i = E[i].next) {
int to = E[i].to;
if (!dfn[to]) {
tarjan(to, x, i);
low[x] = min(low[x], low[to]);
//用来判断是否为割边
if (low[to] == dfn[to]) {
E[i].is = 1;
E[i ^ 1].is = 1;//其反向边也应该添加
}
}
else if (i!= (side^1) ) low[x] = min(low[x], dfn[to]); //修改条件,避免存在重边
}
if (dfn[x] == low[x]) {
int cur = -1;
while (cur!=x) {
cur = S.top();
S.pop();
belong[cur] = connect;
}
connect++;
}
}
int num = 0;
int vis[NMAX];
int dfs(int x, int dst,int & res) {
int ret = 0;
vis[x] = 1;
if (belong[x] == dst) return 1;
for (int i = h[x]; i != -1; i = E[i].next) {
if (!vis[E[i].to]) {
int j = dfs(E[i].to, dst, res);
if (j) ret = j;
if (j && E[i].is) {
res--;
E[i].is = 0;
E[i ^ 1].is = 0;
}
}
}
return ret;
}
int main() {
cin >> F >> R;
while (!(F == 0 && R == 0)) {
memset(h, -1, sizeof(h));
E.clear();
while (!S.empty()) S.pop();
tot = 0;
connect = 0;
for (int i = 0; i < R; i++) {
cin >> a >> b;
addline(a, b);
}
//for (int i = 0; i < E.size(); i++) cout << E[i].from << " " << E[i].to << endl;
//for(int i=h[4];i!=-1;i=E[i].next) cout << E[i].from << " " << E[i].to << endl;
memset(dfn, 0, sizeof(dfn));
for (int i = 1; i <= F; i++) {
if (!dfn[i]) tarjan(i, 0, -1);
}
int Q;
cin >> Q;
int res = connect-1;
num++;
cout << "Case " << num << ":" << endl;
for (int i = 0; i < Q; i++) {
int a, b;
cin >> a >> b;
int A = belong[a];
int B = belong[b];
if (A != B) {
//进行原图的dfs并进行相应的修改
memset(vis, 0, sizeof(vis));
dfs(a, B,res);//引用会一直传递下去吗?
}
cout << res << endl;
}
cout << endl;
cin >> F >> R;
}
}
仍然没有找到哪里存在错误!!!
思路二:
学习别人的思路:lca+并查集
LCA(最近公共祖先)是指在一棵树上,两个节点深度最大的公共祖先节点
用于解决两个节点仅有唯一的一条确定的路径的问题。
在本例中,使用的是暴力的算法,逐步向上求取lca。对于边的删除,利用的是并查集的方式来实现,即每次通过边移动一个点,然后便直接查找其父亲节点,以回避同一个集合中的边。
//AC代码
#include<iostream>
#include<vector>
#include<cstring>
#include<stack>
#define NMAX 100005
using namespace std;
int F, R;
struct edge {
int from, to, next;
int is;//是否为割边
};
vector<edge> E;
vector<edge> Es;
int h[NMAX];
int hs[NMAX];
int a, b;
void addline(int x, int y,vector<edge> & E,int (& h) [NMAX] ) {
edge cur;
cur.from = x, cur.to = y, cur.next = h[x], cur.is = 0;
h[x] = E.size();
E.push_back(cur);
cur.from = y, cur.to = x, cur.next = h[y], cur.is = 0;
h[y] = E.size();
E.push_back(cur);
}
int dfn[NMAX], low[NMAX], belong[NMAX];
stack<int > S;
int tot = 0;
int connect = 0;
void tarjan(int x, int fa, int side) {
dfn[x] = low[x] = ++tot;
S.push(x);
for (int i = h[x]; i != -1; i = E[i].next) {
int to = E[i].to;
if (!dfn[to]) {
tarjan(to, x, i);
low[x] = min(low[x], low[to]);
//用来判断是否为割边
if (low[to] == dfn[to]) {
E[i].is = 1;
E[i ^ 1].is = 1;//其反向边也应该添加
}
}
else if (i!= (side^1) ) low[x] = min(low[x], dfn[to]); //修改条件,避免存在重边
}
if (dfn[x] == low[x]) {
int cur = -1;
while (cur!=x) {
cur = S.top();
S.pop();
belong[cur] = connect;
}
connect++;
}
}
int num = 0;
int vis[NMAX];
int deep[NMAX], fa[NMAX], bf[NMAX];
int dfs(int x, int dst, int& res) {
int ret = 0;
vis[x] = 1;
if (belong[x] == dst) return 1;
for (int i = h[x]; i != -1; i = E[i].next) {
if (!vis[E[i].to]) {
int j = dfs(E[i].to, dst, res);
if (j) ret = j;
if (j && E[i].is) {
res--;
E[i].is = 0;
E[i ^ 1].is = 0;
}
}
}
return ret;
}
void dfs(int x) {
for (int i = hs[x]; i != -1; i = Es[i].next) {
if (!deep[Es[i].to]) {
deep[Es[i].to] = deep[x] + 1;
fa[Es[i].to] = x;
dfs(Es[i].to);
}
}
}
int find(int x) {
if (x != bf[x]) bf[x] = find(bf[x]);
return bf[x];
}
int LCA(int u, int v) {
while (u != v) {
if (deep[u] > deep[v]) u = fa[u];
else if (deep[u] < deep[v]) v = fa[v];
else u = fa[u], v = fa[v];
u = find(u), v = find(v);
}
return u;
}
int main() {
cin >> F >> R;
while (!(F == 0 && R == 0)) {
memset(h, -1, sizeof(h));
memset(hs, -1, sizeof(hs));
E.clear();
Es.clear();
while (!S.empty()) S.pop();
tot = 0;
connect = 0;
for (int i = 0; i < R; i++) {
cin >> a >> b;
addline(a, b, E, h);
}
//for (int i = 0; i < E.size(); i++) cout << E[i].from << " " << E[i].to << endl;
//for(int i=h[4];i!=-1;i=E[i].next) cout << E[i].from << " " << E[i].to << endl;
memset(dfn, 0, sizeof(dfn));
for (int i = 1; i <= F; i++) {
if (!dfn[i]) tarjan(i, 0, -1);
}
//在缩点后的图上来处理
//首先建立缩点后的图
for (int i = 0; i < E.size(); i++) {
if (E[i].is) {
addline(belong[E[i].from], belong[E[i].to], Es, hs );
}
}
//lca一定需要deep和fa的关系吗?->lca是基于树的,因此需要fa的信息,而在暴力搜索lca的过程中,需要向上寻找父节点
memset(deep, 0, sizeof(deep));
for (int i = 0; i < connect; i++) {
bf[i] = i;
if (!deep[i]) deep[i] = 1, fa[i] = -1, dfs(i);
}
int Q;
cin >> Q;
int res = connect-1;
num++;
cout << "Case " << num << ":" << endl;
for (int i = 0; i < Q; i++) {
int a, b;
cin >> a >> b;
int A = find(belong[a]);
int B = find(belong[b]);
if (A != B) {
//首先找公共祖先
int lca = LCA(A, B);
//计算路径上的边的个数
while (A != lca) {
res--;
bf[A] = lca; //A一定是代表元
A = fa[A];
A = find(A);
}
while (B != lca) {
res--;
bf[B] = lca;
B = fa[B];
B = find(B);
}
}
cout << res << endl;
}
cout << endl;
cin >> F >> R;
}
}
对于思路一进行验证,将思路一应用到缩点后的图中,是成立的。
满脸疑惑
#include<iostream>
#include<vector>
#include<cstring>
#include<stack>
#define NMAX 100005
using namespace std;
int F, R;
struct edge {
int from, to, next;
int is;//是否为割边
};
vector<edge> E;
vector<edge> Es;
int h[NMAX];
int hs[NMAX];
int a, b;
void addline(int x, int y,vector<edge> & E,int (& h) [NMAX] ) {
edge cur;
cur.from = x, cur.to = y, cur.next = h[x], cur.is = 0;
h[x] = E.size();
E.push_back(cur);
cur.from = y, cur.to = x, cur.next = h[y], cur.is = 0;
h[y] = E.size();
E.push_back(cur);
}
int dfn[NMAX], low[NMAX], belong[NMAX];
stack<int > S;
int tot = 0;
int connect = 0;
void tarjan(int x, int fa, int side) {
dfn[x] = low[x] = ++tot;
S.push(x);
for (int i = h[x]; i != -1; i = E[i].next) {
int to = E[i].to;
if (!dfn[to]) {
tarjan(to, x, i);
low[x] = min(low[x], low[to]);
//用来判断是否为割边
if (low[to] == dfn[to]) {
E[i].is = 1;
E[i ^ 1].is = 1;//其反向边也应该添加
}
}
else if (i!= (side^1) ) low[x] = min(low[x], dfn[to]); //修改条件,避免存在重边
}
if (dfn[x] == low[x]) {
int cur = -1;
while (cur!=x) {
cur = S.top();
S.pop();
belong[cur] = connect;
}
connect++;
}
}
int num = 0;
int vis[NMAX];
int deep[NMAX], fa[NMAX], bf[NMAX];
int hdfs(int x, int dst, int& res, vector<edge>& E) {
int ret = 0;
vis[x] = 1;
if (x == dst) return 1;
for (int i = hs[x]; i != -1; i = E[i].next) {
if (!vis[E[i].to]) {
int j = hdfs(E[i].to, dst, res, E);
if (j) ret = j;
if (j && !E[i].is) {
res--;
//标记该边已经被删除
E[i].is = 1;
E[i ^ 1].is = 1;
}
}
}
return ret;
}
void dfs(int x) {
for (int i = hs[x]; i != -1; i = Es[i].next) {
if (!deep[Es[i].to]) {
deep[Es[i].to] = deep[x] + 1;
fa[Es[i].to] = x;
dfs(Es[i].to);
}
}
}
int find(int x) {
if (x != bf[x]) bf[x] = find(bf[x]);
return bf[x];
}
int LCA(int u, int v) {
while (u != v) {
if (deep[u] > deep[v]) u = fa[u];
else if (deep[u] < deep[v]) v = fa[v];
else u = fa[u], v = fa[v];
u = find(u), v = find(v);
}
return u;
}
int main() {
cin >> F >> R;
while (!(F == 0 && R == 0)) {
memset(h, -1, sizeof(h));
memset(hs, -1, sizeof(hs));
E.clear();
Es.clear();
while (!S.empty()) S.pop();
tot = 0;
connect = 0;
for (int i = 0; i < R; i++) {
cin >> a >> b;
addline(a, b, E, h);
}
//for (int i = 0; i < E.size(); i++) cout << E[i].from << " " << E[i].to << endl;
//for(int i=h[4];i!=-1;i=E[i].next) cout << E[i].from << " " << E[i].to << endl;
memset(dfn, 0, sizeof(dfn));
for (int i = 1; i <= F; i++) {
if (!dfn[i]) tarjan(i, 0, -1);
}
//在缩点后的图上来处理
//首先建立缩点后的图
for (int i = 0; i < E.size(); i++) {
if (E[i].is) {
addline(belong[E[i].from], belong[E[i].to], Es, hs );
i++;//反向边就不要再建立了
}
}
//cout << "bian" << endl;
//for (int i = 0; i < Es.size(); i++) {
//cout << Es[i].from << " " << Es[i].to << " "<< Es[i].next<< endl;
//}
//lca一定需要deep和fa的关系吗?->lca是基于树的,因此需要fa的信息,而在暴力搜索lca的过程中,需要向上寻找父节点
memset(deep, 0, sizeof(deep));
for (int i = 0; i < connect; i++) {
bf[i] = i;
if (!deep[i]) deep[i] = 1, fa[i] = -1, dfs(i);
}
int Q;
cin >> Q;
int res = connect-1;
num++;
cout << "Case " << num << ":" << endl;
for (int i = 0; i < Q; i++) {
int a, b;
cin >> a >> b;
int A = belong[a];
int B = belong[b];
if (A != B) {
//dfs来搜索到达那里的路径
memset(vis, 0, sizeof(vis));
hdfs(A, B, res,Es);
}
cout << res << endl;
}
cout << endl;
cin >> F >> R;
}
}
本文探讨了两种解决边双连通分量问题的算法:一种基于Tarjan算法和并查集,另一种利用LCA(最近公共祖先)和并查集。第一种方法在原图上进行操作,遇到割边时更新边的状态;第二种方法在缩点后的图上计算LCA,通过调整连接关系减少边的数量。这两种方法在不同的场景下各有优势,对于理解图论算法和实际问题的解决具有重要意义。
227

被折叠的 条评论
为什么被折叠?



