【学习笔记】(28) 基环树
首先,严格地讲,基环树不是树,它是一张有
介绍
无向图上的基环树
有向图上的基环树
内向树
出度为 1
外向树
入度为 1
流程
- 找到唯一的环;
- 对环之外的部分按照若干棵树处理;
- 考虑与环一起计算。
找环
- 从任意一点开始搜索;
- 每次拓展到的点涂为灰色,回溯的点涂为黑色;
- 重复第一、二步,直到通过一条未经过过的边拓展到一个灰色节点;
- 将拓展到的灰色节点涂为红色,函数返回 true;
- 将经过的回溯时经过的节点涂为橙色,函数返回 true;
- 重复第 5 步,直到回溯到被涂为红色的节点,函数返回 false,算法结束。
bool get_ring(int x, int ff){ if (v[x] == 1){ v[x] = 2, v2[x] = 1, rin[++ tot] = x; return true; } v[x] = 1; int opt = 0; for (int i = hd[x]; i;i = e[i].nxt){ if(v_e[i]) continue; v_e[i] = v_e[i ^ 1] = true; int y = e[i].to; if(get_ring(y, x)){ pre_e[y] = i; if (v[x] != 2){ rin[++ tot] = x, v2[x] = 1; return true; } else return false; } } return false; }
对于基环树森林,我们可以用如下几种方法来操作:
- 在环上节点递归前,用另一个 dfs 遍历该节点能到达的所有节点,并标记。
- 在对去掉环之后的若干棵子树进行处理时进行另一种标记 v2[],判断是否是一棵新的基环树的依据为 v2[] 是否被标记。
- 建图时使用并查集。
找环之后的操作就要视题目而定了,不过一般是一个树形DP(子树不考虑环)加一个线性DP+单调队列优化(在环上,断环为链,复制一遍)
例题
Ⅰ. P2607 [ZJOI2008] 骑士
对于环上的每条边,将它断掉,成为一棵树后,直接 没有上司的舞会 DP, 注意有两种情况,不选
#include<bits/stdc++.h> #define N 1000005 #define LL long long using namespace std; inline int read(){ int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();} while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();} return x*f; } int x1,tot,n; int Head[N],to[N<<1],Next[N<<1],val[N],a[N]; bool vis[N]; LL f[N][2],ans; void add(int u,int v){to[++tot]=v,Next[tot]=Head[u],Head[u]=tot;} void dfs(int x){ vis[x]=1,f[x][0]=0,f[x][1]=val[x]; for(int i=Head[x];i;i=Next[i]){ int y=to[i]; if(y==x1) continue; dfs(y); f[x][1]+=f[y][0]; f[x][0]+=max(f[y][1],f[y][0]); } } signed main(){ n=read(); for(int i=1;i<=n;i++){ val[i]=read(); int v=read();add(v,i),a[i]=v; } for(int i=1;i<=n;i++){ if(vis[i]) continue; x1=i; while(!vis[a[x1]]){vis[x1]=1,x1=a[x1];} dfs(x1); LL res=f[x1][0]; x1=a[x1]; dfs(x1); res=max(res,f[x1][0]); ans+=res; } printf("%lld\n",ans); return 0; }
Ⅱ. P1399 [NOI2013] 快餐店
由于这整张图是一个基环树,我们可以考虑每次破掉环上的一条边使它变成一颗树,然后求出直径,最后统计所有的直径中最短的那个作为答案即可。
- 这个直径完全在环上某一点所在的子树中。
- 这个直径从环上某一点所在子树出发,到达该点后在环上走过一些边到达另一个点,进入该点所在子树并且结束。
第一种情况直接统计环上点的子树即可。
第二种情况我们引入一些数组。
树的最大深度。所以我们预先处理环上所有点所在子树的最大深度记为dis_i。
然后我们定义四个数组,
我们可以将 环上的点
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 4e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } bool _u; int n, tot = 1, rcnt; int hd[N], v[N], v2[N], rdis[N], rin[N]; bool v_e[N << 1]; ll ans; ll A[N], B[N], C[N], D[N], dis[N]; // A 前缀 + 子树最大深度 // B 前缀中两个子树的最大深度 + 两点距离 // C 后缀 + 子树最大深度 // D 后缀中两个子树的最大深度 + 两点距离 struct Edge{ int to, nxt, w; void add(int u, int v, int ww){ to = v, nxt = hd[u], hd[u] = tot, w = ww; } }e[N << 1]; bool get_ring(int x){ if (v[x] == 1){ v[x] = 2, v2[x] = 1, rin[++ rcnt] = x; return true; } v[x] = 1; int opt = 0; for (int i = hd[x]; i;i = e[i].nxt){ if(v_e[i]) continue; v_e[i] = v_e[i ^ 1] = true; int y = e[i].to; if(get_ring(y)){ if (v[x] != 2){ rdis[rcnt] = e[i].w; rin[++ rcnt] = x, v2[x] = 1, v[x] = 2; return true; } else return rdis[rcnt] = e[i].w, false; } } return false; } void dfs(int x, int fa){ for(int i = hd[x]; i; i = e[i].nxt){ int y = e[i].to; if(y == fa || v[y] == 2) continue; dfs(y, x); ans = max(ans, dis[x] + dis[y] + e[i].w); //子树内直径 dis[x] = max(dis[x], dis[y] + e[i].w); } } bool _v; int main(){ cerr << abs(&_u - &_v) / 1048576.0 << "MB\n"; n = read(); for(int i = 1; i <= n; ++i){ int u = read(), v = read(), w = read(); e[++tot].add(u, v, w), e[++tot].add(v, u, w); } get_ring(1); for(int i = 1; i <= rcnt; ++i) dfs(rin[i], 0); ll sum = 0, maxn = 0; for(int i = 1; i <= rcnt; ++i){ sum += rdis[i - 1]; A[i] = max(A[i - 1], dis[rin[i]] + sum); B[i] = max(B[i - 1], sum + maxn + dis[rin[i]]); maxn = max(dis[rin[i]] - sum, maxn); } sum = maxn = 0; int tmp = rdis[rcnt]; rdis[rcnt] = 0; for(int i = rcnt; i; --i){ sum += rdis[i]; C[i] = max(C[i + 1], dis[rin[i]] + sum); D[i] = max(D[i + 1], sum + maxn + dis[rin[i]]); maxn = max(dis[rin[i]] - sum, maxn); } ll ans2 = B[rcnt]; for(int i = 1; i < rcnt; ++i) ans2 = min(max(max(B[i], D[i + 1]), A[i] + C[i + 1] + tmp), ans2); printf("%lld\n", max(ans, ans2)); return 0; }
Ⅲ.AT_arc079_d [ARC079F] Namori Grundy
题意:有一个弱联通的有向图,含有
对于树上的点,显然可以直接做,对于环上的,分类讨论:
, 。 , 不用操作。
发现无解的情况只有环上的
#include<bits/stdc++.h> #define ll long long using namespace std; const int N = 2e5 + 67; int read(){ int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){if(ch == '-') f = -f; ch = getchar();} while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar();} return x * f; } bool _u; int n, tot, cnt; int fa[N], hd[N], jud[N], val[N]; bool vis[N], cir[N]; struct Edge{ int to, nxt; }e[N << 1]; void add(int u, int v){e[++tot].to = v, e[tot].nxt = hd[u], hd[u] = tot;} void dfs(int x){ for(int i = hd[x]; i; i = e[i].nxt){ int y = e[i].to; if(!cir[y]) dfs(y); } for(int i = hd[x]; i; i = e[i].nxt){ int y = e[i].to; if(!cir[y]) jud[val[y]] = 1; } for(; jud[val[x]]; ++val[x]) ; for(int i = hd[x]; i; i = e[i].nxt){ int y = e[i].to; if(!cir[y]) jud[val[y]] = 0; } } bool _v; int main(){ cerr << abs(&_u - &_v) / 1048576.0 << "MB\n"; n = read(); for(int i = 1; i <= n; ++i) fa[i] = read(), add(fa[i], i); int p = 1; for(; !vis[p]; p = fa[p]) vis[p] = 1; for(; !cir[p]; p = fa[p]) cir[p] = 1; int maxn = -1, minn = 1e9; for(int i = 1; i <= n; ++i) if(cir[i]){ ++cnt; dfs(i); maxn = max(maxn, val[i]), minn = min(minn, val[i]); } if(minn == maxn && cnt & 1) printf("IMPOSSIBLE\n"); else printf("POSSIBLE\n"); return 0; }
本文作者:南风未起
本文链接:https://www.cnblogs.com/jiangchen4122/p/17716010.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步