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';
}