树形 DP
你是一位想象学竞赛生。今天你看到了如下题目:
有 \(n\) 个人,他们之间有师生关系,一次出行中,每个人都有自己的喜悦度且每个人都不会和自己的老师出行,求最大喜悦度。
你作为一位想象学竞赛生,一下就想到了方法,可以对每个师生关系都连一条边,设 \(dp_{i,0/1}\) 表示是否选第 \(i\) 个节点,\(0\) 表示不选,\(1\) 表示选。
于是,你推出了式子:
\(dp_{i,0}=\sum^{z_i}_{j=1}\max(dp_{s_{i,j},0},dp_{s_{1,j},1}),dp_{i,1}=\sum^{z_i}_{j=1}dp_{s_{i,j},0}\)
其中,\(z_i\) 表示 \(i\) 的子节点数,\(s_i,j\) 表示 \(i\) 的第 \(j\) 个子节点。
你使用深搜先更新子节点,再更新父节点。
于是,你轻松地把这题切掉了。
#include<bits/stdc++.h>
using namespace std;
vector<int>g[6666];
int dp[6666][2],n,w[6666],x,y,root;
bool r[6666];
void dfs(int pos) {
dp[pos][1]=w[pos];
for(int i=0;i<g[pos].size();i++) {
dfs(g[pos][i]);
dp[pos][0]+=max(dp[g[pos][i]][0],dp[g[pos][i]][1]);
dp[pos][1]+=dp[g[pos][i]][0];
}
}
int main() {
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<n;i++) {
cin>>x>>y;
g[y].push_back(x);
r[x]=true;
}
root=1;
while(r[root])root++;
dfs(root);
cout<<max(dp[root][0],dp[root][1]);
}
然后,你打开了推荐题目,准备继续切水题。
你又看到了如下题目:
有 \(n\) 个想象学竞赛生,第 \(i\) 位的能力为 \(a_i\),他们之间有 \(n-1\) 个关系,切断一些关系,使得剩下的竞赛生们能力和最大。
这题似乎和上题有点像,你设 \(dp_i\) 为第 \(i\) 位竞赛生不被 AFO 时的最大能力和。
很快,你就推出了式子:
\(dp_i=\sum^{z_i}_{j=1}\max(dp_{s_{i,j}},0)\)
你发现,同样可以用深搜处理。
于是,你在 5min 内又 A 了这题。
#include<bits/stdc++.h>
using namespace std;
vector<int>g[16005];
int dp[16005],n,w[16005],x,y,root,ans;
void dfs(int pos,int f) {
dp[pos]=w[pos];
for(int i=0;i<g[pos].size();i++) {
int t=g[pos][i];
if(f!=t)dfs(t,pos);
if(f!=t)dp[pos]+=max(0,dp[t]);
}
}
int main() {
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<n;i++) {
cin>>x>>y;
g[y].push_back(x);
g[x].push_back(y);
}
dfs(1,0);
ans=-1145141919;
for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
cout<<ans;
}
你在讨论区发现了双倍经验:
\(n\) 场 % 你赛,由 \(n-1\) 个关系关联,第 \(i\) 个 % 你赛可以增加 \(a_i\) 点能力,删除一些 % 你赛,使得最后的能力值最大。
对比上一题,你发现仅仅只是少了最终的集合不能为空这一条件,于是你轻松 A 掉了它。
#include<bits/stdc++.h>
using namespace std;
vector<int>g[114000];
long long dp[114000],n,w[114000],x,y,root,ans;
void dfs(int pos,int f) {
dp[pos]=w[pos];
for(int i=0;i<g[pos].size();i++) {
int t=g[pos][i];
if(f!=t)dfs(t,pos);
if(f!=t)dp[pos]+=max(0ll,dp[t]);
}
}
int main() {
cin>>n;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<n;i++) {
cin>>x>>y;
g[y].push_back(x);
g[x].push_back(y);
}
dfs(1,0);
ans=0;
for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
cout<<ans;
}
你继续去找这一类的水题切。
然后你看到了如下题目:
还没 AFO 的想象学竞赛生有 \(n\) 个,他们之间有 \(n-1\) 个关系。第 \(i\) 个人可以花费 \(a_i\) 的代价学习新知识,然后传授给与自己有关系的人。求最少要有多少人去学习,才能都学到新知识。
你建出一颗数表示关系,然后使用 \(dp_{i,1\sim 3}\)每个竞赛生的知识由来:由父亲节点来,由自己来,由儿子节点来。
然后快速推出了式子:
\(dp_{i,1}=\sum\min(dp_{son,1},dp_{son,3})\)
\(dp_{i,2}=\sum\min(dp_{son,1},dp_{son,2},dp_{son,3})+a_i\)
\(dp_{i,3}=\sum\min(dp_{son,1},dp_{son,3})\)
你同样使用了深搜处理。
交上去,发现 WA 了一些点,你追踪代码,发现 \(dp_{i,3}\) 需要小特判,加上后你就轻松拿下了这题。
#include<bits/stdc++.h>
using namespace std;
const int N=1505,M=1000005;
int n,h[N],e[M],ne[M],idx,f[N][3],w[N];
bool vis[N];
void add(int a, int b) {
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u) {
if(vis[u])return;
vis[u]=1;
int sum=0,cnt=0x3f3f3f3f;
for (int i=h[u]; ~i; i=ne[i]) {
int j=e[i];
if(vis[j]) continue;
dfs(j);
f[u][0]+=min(f[j][0],min(f[j][1],f[j][2]));
f[u][1]+=min(f[j][0],f[j][2]);
if (f[j][0]<f[j][2])sum++;
else cnt=min(cnt,f[j][0]-f[j][2]);
f[u][2]+=min(f[j][0], f[j][2]);
}
if(!sum)f[u][2]+=cnt;
return;
}
int main() {
memset(h,-1,sizeof h);
cin>>n;
for (int i=1; i<=n; i++) {
int m,id;
cin>>id>>f[id][0]>>m;
int tmp=id;
while (m--)cin>>id,add(id,tmp),add(tmp,id);
}
dfs(1);
cout<<min(f[1][0],f[1][2]);
}
你又找到了一道题:
\(n\) 个想象学竞赛生在机房,第 \(i\) 位的能力为 \(a_i\),他们之间有 \(n-1\) 个关系。老师要 AFO 一些学生,使得机房内的学生互相没有关系,也就不会互相打扰,此时机房能力值为未 AFO 学生的能力值之和。求出所有 AFO 方案得到得机房能力值之和。
你设 \(dp_i\) 为 \(pos\) 的答案,\(dp2_i\) 为只考虑 \(pos\) 子树时,分配方案的数量。
可得:
\(dp_i=dp_{i}\times dp2_{son}+dp_{son}\times dp2_{i}+dp_i+dp_{son}\)
\(dp2_i=dp2_{i}\times dp2_{son}+dp2_{i}+dp2_{son}\)
你选择深搜来更新 \(dp\) 数组。
于是你一眼秒了这题。
#include<bits/stdc++.h>
using namespace std;
const long long mod=100000007;
vector<int>g[114000];
long long dp[114000],dp2[114000],n,w[114000],x,y,key;
void dfs(int pos,int f) {
for (int i=0; i<g[pos].size(); i++) {
int t=g[pos][i];
if (t!=f) {
dfs(t,pos);
dp[pos]=(dp[t]*dp2[pos]+dp[pos]*dp2[t]+dp[pos]+dp[t])%mod;
dp2[pos]=(dp2[pos]+dp2[t]*dp2[pos]+dp2[t])%mod;
}
}
dp[pos]=(long long)(pow(pos,key)+dp[pos])%mod;
dp2[pos]=(dp2[pos]+1)%mod;
return;
}
int main() {
cin>>n>>key;
for(int i=1; i<n; i++) {
cin>>x>>y;
g[y].push_back(x);
g[x].push_back(y);
}
dfs(1,0);
cout<<dp[1];
}
你学会了树形 DP,还 A 了 \(5\) 题,却高兴不起来。
因为,
你们想象学校队刚建立时,机房出游,大家都很高兴。
之后的一场 % 你赛,刷掉了一堆人。
大家为了不 AFO,天天打 % 你赛。
为了加快速度,大家互相传授想象学知识。
之后,老师只留下了你一人,这样,你就不会被他人影响了。
五道题,看似是题,却处处都像你。
或许在想象学的路上,你要孤独前行。

浙公网安备 33010602011771号