【图论】基环树

参考@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;
}  
相关推荐
插件开发5 分钟前
矢量路径运算如何选GPU技术?——适用算法对比及OpenGL/Direct3D/CUDA选型指南
算法·3d
8Qi816 分钟前
LeetCode 72:编辑距离(Edit Distance)—— 题解
算法·leetcode·职场和发展·动态规划
SoftLipaRZC23 分钟前
顺序表的应用:通讯录项目与经典算法实战
算法
8Qi823 分钟前
LeetCode 583. 两个字符串的删除操作
算法·leetcode·职场和发展·动态规划
tigershang27 分钟前
卡尔曼滤波:不确定世界中的最优估计
人工智能·算法·机器学习
一个儒雅随和的男子41 分钟前
限流算法详细剖析
java·服务器·算法
工业胶粘剂技术2 小时前
单组分高温环氧结构胶 K-EP280 完整技术参数与工程选型分析
算法·制造
小欣加油3 小时前
Leetcode31 下一个排列
数据结构·c++·算法·leetcode·职场和发展
_日拱一卒3 小时前
LeetCode:39组合总和
java·算法·leetcode·职场和发展
无限进步_3 小时前
【Linux】进程状态、僵尸与孤儿、进程调度
linux·运维·服务器·开发语言·数据结构·算法