最近在洛谷上写树上dp,遇到了一个叫基环树的东东。
蒟蒻不自量力,也来写一篇合集 吧。
什么是基环树?
基环树,也是环套树,简单地讲就是树上在加一条边。它形如一个环,环上每个点都有一棵子树的形式。因此,对基环树的处理大部分就是对树处理和对环处理。显然,难度在于后者。
一般来说,这种题目的做法都是先找到环,断开环中的任意一条边, 把它当成一般的树形DP来做。
步骤一:找环,我们可以用dfs记录前驱,标记访问状态来找环,如果搜到某点已经访问过了,说明这个访问过的点以及其前驱必在环上。
步骤二:断环,断开这条边,以这两个点作为根节点分别dp。最后根据题目情况找一下答案就好
例题1:题目链接
给一颗含有点权的基环外向树,可以选任意数目的顶点。
假如两个点之间有一条边连接,如果选择了其中一端的节点,那另一段的节点则不可选择,求最大点权和。
思路:dfs找环,然后取环上任意2点,断开它们的边,分别dp
dp[rt][1] dp[rt][0]表示取和不取rt 以rt为根的树的最优值
转移过程:dp[rt][1]=p[rt]+Σdp[child][0]
dp[rt][0]=Σ max(dp[child][1],dp[child][0])
细节见注释
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<stdlib.h>
#include<string.h>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<math.h>
#include<set>
using namespace std;
#define LL long long
#define ULL unsigned long long
const int INF=3000;
const double eps=1e-5;
const int maxn=1e5+50;
//基环树dp
//n个点 n个边构成的一个有环的树
struct
{
int to,next;
}edge[maxn*2];
int cnt=1,head[maxn];
void add(int from,int to)
{
edge[cnt].to=to;
edge[cnt].next=head[from];
head[from]=cnt++;
}
int n;
double k;
int dp[maxn][2];
int p[maxn];
bool vis[maxn];
bool flag;
int ans;
void dfs(int rt,int par,int no)//断开边的操作就是,再开一个变量no,表示no为根节点,其它点都不能转移到根节点
{
dp[rt][1]=p[rt];
dp[rt][0]=0;
for(int i=head[rt];i;i=edge[i].next)
{
int to=edge[i].to;
if(to==par || to==no) continue;
dfs(to,rt,no);
dp[rt][1]+=dp[to][0];
dp[rt][0]+=max(dp[to][1],dp[to][0]);
}
}
void find_cir(int rt,int par)
{