参考@TiAmoZhang 的帖子
参考@I_LOVE_MATH 的文章
概念
一个环上面挂了很多子树,有n个节点和n条边的图 (如果不保证连通的话,那么整张图是一张基环树森林)
并且如果将环上的任意一条边去除,那么整棵基环树会成为一棵普通的树。
第一步找环
- 有向直接DFS,回到根就是一个环
- 无向可以使用并查集,不会有重边,所以有集内的边就形成了环
之后可以把环上任意一条边去除变树,转化为树上问题
典:P2607 [ZJOI2008] 骑士

代码建有向图 DFS找环
cpp
const int N = 1e6+10,M=1e5;
const double PI=acos(-1),eps=1e-12;
const long long mod =998244353, inf = 1e18+10 ,up=1e9;
int a[N],vis[N];
vector<int>g[N];// 有向边
/*
直观来想 答案和树的深度有关
找环 以环上两点分别作为根 去掉环上一条边形成树 会有两种答案(奇/偶深度) 环上其他边去掉也一样
树形dp找最大的答案
*/
int r1,r2;
void dfs(int now,int rt){// dfs找环
vis[now]=1;
for(auto x:g[now]){
if(x==rt){
r1=x,r2=now;
return;
}
if(vis[x])continue;
dfs(x,rt);
}
}
int dp[N][2];
int dfs2(int now,int rt){// 树形dp
// vis2[now]=1;
dp[now][1]=a[now];
dp[now][0]=0;
for(auto x:g[now]){
if(x==rt)continue;// 因为一个联通集要跑两边 不用vis记号了 直接看根来判环
dfs2(x,rt);
// += 算总子树和
dp[now][0]+=max(dp[x][0],dp[x][1]);// 本次不选 子树可选可不选
dp[now][1]+=dp[x][0];// 本次选 孩子不选
}
return dp[now][0];// 直接用函数执行后的返回值 因为每次dfs2都会改变dp
}
void solve(){
int n;cin>>n;
forr(i,1,n){
int hate;
cin>>a[i]>>hate;
g[hate].push_back(i);
}
int sm=0;
forr(i,1,n){
if(vis[i])continue;
/*
//一定会找到环的吧
dfs(i,i);
cout<<r1<<' '<<r2<<endl;
sm+=max(dfs2(r1,r1),dfs2(r2,r2));//r1 r2不能同时选
不一定 可能这个点在环外 但是和环联通
在之前找环的时候可能遍历不到 从这个点出发也找不到之前找完的环
如 2 1 2 2的3、4点
*/
r1=r2=0;
dfs(i,i);
// cout<<r1<<' '<<r2<<endl;
if(r1){
dfs2(r1,r1);
sm+=max(dfs2(r1,r1),dfs2(r2,r2));//r1 r2不能同时选 返回的是dp[rt][0]
}
}
cout<<sm<<endl;
}
2025ZJCPC L. Nailoongs Always Lie

有自环,但是对答案无贡献,建有向图,找环方法用了并查集,答案找法类似上面典题。
cpp
const int N = 1e5+10,M=1e5;
const double PI=acos(-1);
const long long mod =998244353, inf = 2e18 ;
vector<int>g[N];
// 并查集找环
int fa[N],vis[N],cnt[N][2];// cnt[][1奶龙/0不是]
int findf(int x){
return fa[x]=(fa[x]==x?x:findf(fa[x]));
}
int dfs(int now,int f){// 树形dp找答案
vis[now]=1;
cnt[now][0]=0;
cnt[now][1]=1;
for(auto x:g[now]){
if(x==f||vis[x])continue;
dfs(x,now);
cnt[now][0]+=max(cnt[x][0],cnt[x][1]);
cnt[now][1]+=cnt[x][0];
}
vis[now]=0;// 回复现场
return cnt[now][0];
}
void solve(){
/*
自环 自己说自己是奶龙 必然是说假话的其他生物
*/
int n;cin>>n;
forr(i,1,n)fa[i]=i;
vector<int>a(n+1);
vector<pii>cir;
forr(i,1,n){
cin>>a[i];
g[a[i]].push_back(i);
int fx=findf(a[i]),fy=findf(i);
if(fx!=fy)fa[fy]=fx;
else cir.push_back({a[i],i});
}
int ans=0;
for(auto [u,v]:cir){
ans+=max(dfs(u,0),dfs(v,0));
}
cout<<ans<<endl;
}
