基环树
1.随机化算法2.组合数学3.概率和期望4.树链剖分5.矩阵乘法与矩阵快速幂6.最短路7.卡特兰数、Prüfer 序列、BSGS8.分块和莫队9.AC 自动机10.平衡树11.基础字符串算法12.基础数论13.Miller-Rabin 与 Pollard-Rho14.广义后缀自动机15.后缀自动机 SAM16.回文自动机 PAM17.Manacher18.后缀数组19.01 分数规划20.网络流21.整体二分22.cdq 分治23.点分治24.虚树25.扫描线26.笛卡尔树
27.基环树
28.树哈希29.莫比乌斯反演30.二分图31.朱刘算法32.最大团33.杂项34.杜教筛35.拉格朗日插值36.线段树综合37.可持久化数据结构38.K-D Tree39.Burnside 引理与 Polya 定理40.线性基41.替罪羊树42.LCT43.插头 dp44.原根45.多项式乘法46.斯特林数47.二项式反演与斯特林反演48.Min-Max 容斥49.辛普森积分法50.Min_25 筛51.凸包52.2-SAT1 基环树概念
1.1 定义
首先,基环树并不是一颗严格的树。它是一张由
1.2 无向联通图上的基环树
首先,一棵树有
如下图所示:
1.3 有向联通图上的基环树
有向图上根据边的方向再次分为两类
1.3.1 内向树
每个节点的出度为
1.3.2 外向树
每个节点的入度为
1.4 非联通图上的基环树
如果整个图非联通,那么又会形成基环树森林、基环外向树森林、基环内向树森林。
2 基环树处理方式
上面的定义其实对于做题没有什么帮助,同时基环树的题目通常也要结合树形或环形 dp 求解。下面介绍一些基本处理方式。
2.1 找环
最基本的一步:先找出基环树中环在哪里。
以无向连通图为例,代码很好想也很好实现:
void dfs(int u, int fa) {
if(ins[u]) {//在栈内,说明又走回来了
int v;
mark[u] = 1;
while(s[top] != u) {//这些节点均为环上的点
mark[s[top]] = 1;
top--;
}
return ;
}
if(vis[u] == 1) return ;//已经访问(访问并不等价于在栈中)
s[++top] = u;
vis[u] = ins[u] = 1;
for(int i = head[u]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fa) continue;
dfs(to, x);
}
top--;
ins[u] = false;//弹出栈
return;
}
2.2 树形 dp 处理
由于基环树与普通的树十分接近,因此可以考虑去掉环上的一条边,使整张图变为一颗树之后再开始 dp。
又因为我们删去了一条边,所以会有两个点的关系被删除,所以这种方法一般要 dp 两次。
2.3 环形 dp 处理
首先我们将环拎出来,这样就可以看成是一些树挂在这个环上。我们将所有子树的信息算出来之后合并到环上的节点,这样就可以环形 dp 求解了。
3 例题
对于基环树,概念是没有什么用的,所以直接看一道例题。
3.1 [ZJOI2018] 骑士
首先这道题和 没有上司的舞会 很像,只是变成了基环树。
我们考虑树形 dp 处理,先求出环上的一条边,以这条边上的两点作树形 dp,分别得到
由于两者只能取一,所以在两个答案中取
剩下的注意细节。
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef long long LL;
const int Maxn = 2e6 + 5;
int n, w[Maxn];
int head[Maxn], edgenum = 1;
struct node {
int nxt, to;
}edge[Maxn];
void add(int from, int to) {
edge[++edgenum] = {head[from], to};
head[from] = edgenum;
}
int st, ed, num;
int vis[Maxn], ins[Maxn];
void fcir(int x, int fa) {
if(vis[x]) return ;
vis[x] = ins[x] = 1;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fa) continue;
if(ins[to]) {
st = x, ed = to, num = i;
continue;
}
fcir(to, x);
}
ins[x] = 0;
}
int dp[Maxn][2];
void dfs(int x, int fa) {
dp[x][0] = 0;
dp[x][1] = w[x];
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(to == fa) continue;
if(i == num || (i ^ 1) == num) {
continue;
}
dfs(to, x);
dp[x][0] += max(dp[to][0], dp[to][1]);
dp[x][1] += dp[to][0];
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
int v;
cin >> w[i] >> v;
add(i, v), add(v, i);
}
int ans = 0;
for(int i = 1; i <= n; i++) {
if(vis[i]) continue;
fcir(i, 0);
dfs(st, 0);
int ans1 = dp[st][0];
memset(dp, 0, sizeof dp);
dfs(ed, 0);
ans += max(ans1, dp[ed][0]);
}
cout << ans ;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律