题解:P10933 创世纪
xf20280111 · · 题解
题意
可以抽象成一个图论问题。
给你
思路
可以想到 DP。
这是一棵树吗?显然不是,因为边数等于点数,那就是一棵基环树了!!!
基环树 DP 的一般思路就是先断掉一条边,使其变成一个普通的树形 DP 进行求解,最后考虑上删边的影响。
那我们就先考虑这个树上的 DP。
树上 DP
和没有上司的舞会很像。
状态
设
算是一个经典模型了,一般都会这么设。
转移方程
如果
如果
边界条件
因为要求和,所以全赋值为
删边
用两次 DP 来解决。
考虑
最后答案取最大值就行了。
代码
细节都在注释里。
#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;
}