T1.洛谷P2014-选课
最水的树形DP。
每一层直接先不选根节点,把孩子节点当做背包选进来,最后统一把根节点加进去(保证不会出现状态重叠)。
水的原因是每个节点花费都是1。
#include<bits/stdc++.h>
using namespace std;
struct Xjh{
int Ver,Next;
}E[601];
int Head[601],Cnt,n,m,A[601],Opt[601][601],Root,Ans;
int x,y;
void Add_Edge(int x,int y){
E[++Cnt].Ver=y;
E[Cnt].Next=Head[x];
Head[x]=Cnt;
}
void Dfs(int u){
Opt[u][0]=0;
for(int i=Head[u],v;i;i=E[i].Next){
Dfs(v=E[i].Ver);
for(int t=m;t;--t)
for(int j=t;j;--j)
Opt[u][t]=max(Opt[u][t],Opt[u][t-j]+Opt[v][j]);
}
if(u!=0)
for(int t=m;t;--t)
Opt[u][t]=Opt[u][t-1]+A[u];
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i){
int j;
scanf("%d%d",&j,&A[i]);
Add_Edge(j,i);
}
Dfs(0);
printf("%d\n",Opt[0][m]);
return 0;
}
T2.洛谷P1270-“访问”美术馆
第二道水题。
直接记忆化搜索,水的原因是在选画的时候可以直接贪心,不用再DP,也就不会有状态的重叠。
#include<bits/stdc++.h>
#define Max(x,y) x>y?x:y
#define Min(x,y) x<y?x:y
using namespace std;
int Lft[501],Rgt[501],Pic[501],Way[501],Cnt;
int Opt[501][601];
int T;
void Init(int u){
scanf("%d",&Way[u]);
scanf("%d",&Pic[u]);
if(!Pic[u]){
Lft[u]=++Cnt;
Init(Cnt);
Rgt[u]=++Cnt;
Init(Cnt);
}
}
int Dfs(int u,int t){
if(t<2*Way[u])
return 0;
if(Opt[u][t])
return Opt[u][t];
if(Pic[u])
return Opt[u][t]=Min(Pic[u],(t-2*Way[u])/5);
for(int i=0;i<=t-2*Way[u];++i)
Opt[u][t]=Max(Opt[u][t],Dfs(Lft[u],i)+Dfs(Rgt[u],t-2*Way[u]-i));
return Opt[u][t];
}
int main(){
scanf("%d",&T);
T--;
Init(0);
Dfs(0,T);
printf("%d\n",Opt[0][T]);
return 0;
}
T3.洛谷P3360-偷天换日
是T2的改编版,难度提升。
在每个展览厅偷画的时候要跑01背包,于是没有办法记忆化搜索(因为在展厅里记忆化的话,递归下去会出现后效
性,递归下去可以选的物品无法确定(因为在这一层选了下一层就无法选))。
所以只能一次性把每个节点所有状态都跑掉。
#include<bits/stdc++.h>
#define Max(x,y) x>y?x:y
#define Min(x,y) x<y?x:y
using namespace std;
int Lft[501],Rgt[501],Pic[501],Way[501],Cnt;
int Opt[501][601];
int Cost[31],Val[31];
int T;
void Init(int u){
scanf("%d",&Way[u]);
scanf("%d",&Pic[u]);
if(Pic[u]){
for(int i=1;i<=Pic[u];++i)
scanf("%d%d",&Val[i],&Cost[i]);
for(int i=1;i<=Pic[u];++i)
for(int j=T;j>=Cost[i];--j)
if(j-Cost[i]>=2*Way[u])
Opt[u][j]=Max(Opt[u][j],Opt[u][j-Cost[i]]+Val[i]);
}
else{
Lft[u]=++Cnt;
Init(Cnt);
Rgt[u]=++Cnt;
Init(Cnt);
for(int i=Way[u]*2;i<=T;++i)
for(int j=0;j<=i-2*Way[u];++j)
Opt[u][i]=Max(Opt[u][i],Opt[Lft[u]][j]+Opt[Rgt[u]][i-2*Way[u]-j]);
}
}
int main(){
scanf("%d",&T);
T--;
Init(0);
printf("%d\n",Opt[0][T]);
return 0;
}
T4.洛谷P1064-金明的预算方案
这怕是这篇博客里的Rank1难题了(~~绿题吊打三蓝题手动滑稽~~ ) 。
其实大概有更简单的做法,因为附件最多两个并且附件没有附件。
树形DP的做法可以严格加强这道题,附件数量无限制,附件可以有附件。
与T1的差别在于T1中每个根的体积都是1,而本题体积不一(最重要的),并且价值不一。
与T1一样,由于防止状态出现重叠,对于每个节点(看作子树的根),一开始不能选根,只能先把子树的最大值都跑
出来,然后再用子树的最大值跑01背包。
T1中的根是在当前DP值更新完了之后加进去的,这一题是等到上一层更新时再加进去的,都能保证根只被选一次,
效果等同。
#include<bits/stdc++.h>
using namespace std;
int Fa[61];
int Opt[61][32001];
int Val[61],Cost[61];
vector<int>G[61];
int n,m;
void Dfs(int u,int t){
if(t==0)
return;
for(int k=0;k<G[u].size();++k){
int v=G[u][k];
for(int i=0;i<=t;++i)
Opt[v][i]=Opt[u][i];
Dfs(v,t-Cost[u]);
for(int i=t;i>=Cost[v];--i)
Opt[u][i]=max(Opt[u][i],Opt[v][i-Cost[v]]+Cost[v]*Val[v]);
}
}
int main(){
scanf("%d%d",&m,&n);
for(int i=1;i<=n;++i){
scanf("%d%d%d",&Cost[i],&Val[i],&Fa[i]);
G[Fa[i]].push_back(i);
}
Dfs(0,m);
printf("%d\n",Opt[0][m]);
return 0;
}