P10933 创世纪 题解

P10933 创世纪 题解

题目传送门

此题和[ZJOI2008] 骑士有点像,考虑基环树上跑DP。

为了理解方便,我们对原图建反图。即对于一条边 \(u\to v\) 其表示 \(v\) 可以限制 \(u\),此时题面可理解为”要求满足图上每个节点至少有一个子节点不被取用,求最多可取用的节点的数量“

首先考虑树上DP,定义 \(dp_{0,x}\) 表示树上不取用(不投放)点 \(x\) 时,以 \(x\) 为根的子树中最多可投放的元素个数。与此类似,\(dp_{1,x}\) 表示取用(投放)点 \(x\) 的情况。

再通过树上DP将信息向根传递,最后 \(max(dp_{0,root},dp_{1,root})\) 即为整棵树上的答案。

对于 \(dp_{1,x}\) 的转移,可以在其子树中选定一点 \(p\),强制其不被选取。其相较 \(dp_{0,x}\) 无限制的选取作出的负贡献为 \(max(dp_{0,p},dp_{1,p})-dp_{0,p}\),由此可推出转移方程

\[\ dp_{0,x}=\sum\limits_{p\in son(x)} max(dp_{0,p},dp_{1,p}) \]

\[dp_{1,x}=1+dp_{0,x}-\min\limits_{p\in son(x)} \{ max(dp_{0,p},dp_{1,p})-dp_{0,p} \} \]

我们再考虑如何处理图上的环。注意到可以选取环上相邻两点,并分别以其为根跑树上DP,当访问到自己时令其 \(dp_{1,root}\) 为极小值,最后从两根中统计答案。

关于环上相邻两点的寻找,可以使用并查集。在建边过程中,如果在连边前两点已经联通,加上此边后其一定为环上相邻两点。

Talk is cheap. Show me the code.

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=1e6+10;
int n,fa[inf],dp[2][inf],ans,root;
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
vector<int>w[inf];
vector<pair<int,int>> ring;
void dfs(int x){
	int minn=LONG_LONG_MAX;
	dp[0][x]=0;
	for(auto p:w[x]){
		if(p==root){
			dp[1][p]=LONG_LONG_MIN;
			continue;
		}
		dfs(p);
		dp[0][x]+=max(dp[1][p],dp[0][p]);
		minn=min(minn,max(dp[0][p],dp[1][p])-dp[0][p]);
	}
	dp[1][x]=1+dp[0][x]-minn;
}
signed main(){
	// freopen("a.in","r",stdin);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1,a;i<=n;i++){
		cin>>a;
		w[a].push_back(i);
		int fu=find(i),fv=find(a);
		if(fu==fv) ring.push_back(make_pair(i,a));
		else fa[i]=a;
	}
	for(auto p:ring){
		root=p.first;
		dfs(root);
		int kep=max(dp[0][root],dp[1][root]);
		root=p.second;
		dfs(root);
		ans+=max(kep,dp[1][root]);
	}
	cout<<ans<<'\n';
}
posted @ 2025-06-28 15:49  Gon-Tata  阅读(19)  评论(1)    收藏  举报