树形 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,天天打 % 你赛。
为了加快速度,大家互相传授想象学知识。
之后,老师只留下了你一人,这样,你就不会被他人影响了。

五道题,看似是题,却处处都像你。

或许在想象学的路上,你要孤独前行。

posted @ 2024-12-06 19:45  ni_ju_ge  阅读(24)  评论(0)    收藏  举报