专栏文章

题解:P10933 创世纪

P10933题解参与者 3已保存评论 3

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
3 条
当前快照
1 份
快照标识符
@mioyq12k
此快照首次捕获于
2025/12/03 03:20
3 个月前
此快照最后确认于
2025/12/03 03:20
3 个月前
查看原文

题意

可以抽象成一个图论问题。
给你 nn 个点的有向图,每个点恰好有一个出边(可能有自环),选出尽可能多的点,使得每个被选出的点 xx,都至少有一个指向 xx 的点没被选。

思路

可以想到 DP。
这是一棵树吗?显然不是,因为边数等于点数,那就是一棵基环树了!!!
基环树 DP 的一般思路就是先断掉一条边,使其变成一个普通的树形 DP 进行求解,最后考虑上删边的影响。
那我们就先考虑这个树上的 DP。

树上 DP

和没有上司的舞会很像。

状态

dpu,0/1dp_{u,0/1} 表示以 uu 为根的子树,并且选或不选 uu 最多能选择的点数。
算是一个经典模型了,一般都会这么设。

转移方程

如果 u=0u = 0,那么他的儿子都可以选,当然也可以不选。所以求最大值。转移方程就是 dpu,0=max(dpv,0,dpv,1)dp_{u,0}=\sum{\max(dp_{v,0},dp_{v,1})}
如果 u=1u = 1,那么他至少要有一个儿子不选,不选的儿子肯定是值最小的,这样 dpu,1dp_{u,1} 才会大。即 dpu,1=1+dpu0min(dpv,1dpv,0)dp_{u,1} = 1 + dp_{u}{0} - \min(dp_{v,1} - dp_{v,0})

边界条件

因为要求和,所以全赋值为 00,即 dpu,0/1=0dp_{u,0/1} = 0

删边

用两次 DP 来解决。
考虑 uu 选不选,一次设 uu 要选,一次设 uu 不选。
最后答案取最大值就行了。

代码

细节都在注释里。
CPP
#include<bits/stdc++.h>

using namespace std;

using ll = long long;

const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;

int n,a[N];

vector<int> e[N];

int dfn[N],dp[N][2];

int vis[N];
void dfs(int u,int root,bool flag){
	dfn[u] = true;
	int mi = INF;
	for (auto v :e[u]){
		if (v == root) continue;//防止死循环
		dfs(v,root,flag);
		dp[u][0] += max(dp[v][1],dp[v][0]);
		mi = min(dp[v][1] - dp[v][0],mi);//维护最小值
	}
	dp[u][1] = 1 + dp[u][0];
	if (u != a[root] or !flag){	
		if (mi >= 0) dp[u][1] -= mi;
	}
}
int main()
{
	int n;cin >> n;
	for (int i = 1;i <= n;i++){
		cin >> a[i];
		e[a[i]].push_back(i);//还要在树的内部 DP,所以建反图
	}
	int sum = 0;
	for (int x = 1;x <= n;x++){//基环森林
		if (!dfn[x]){
			while(!vis[x]){
				vis[x] = true;
				x = a[x];
			}//求环
			dfs(x,x,1);
			int ans = dp[x][0];
			dfs(x,x,0);
			sum += max(dp[x][1],ans);
		}

	}
	cout << sum;
	return 0;	
} 

评论

3 条评论,欢迎与作者交流。

正在加载评论...