CF815C Karen and Supermarket
树上DP。
首先,考虑建边。显然连一条$x[i]->i$的边。
其次,考虑DP。那么有数组$f[i][j][k]$表示以$i$为根节点的子树中,选择$j$件物品的代价;$k$代表是否使用折扣。
最后看输出。那就看以$1$为根节点,选择$ans$件物品,无论打不打折,如果代价小于$b$,则可行。此外,为了使$ans$尽量大,所以我们要从$n$到$1$遍历寻找答案。
#include<bits/stdc++.h>
#define N 5010
using namespace std;
int n,b,cnt,ans;
int c[N],d[N],x[N],head[N],siz[N],f[N][N][2]; //f[i][j][k] 以i为根节点的子树中,选择j件物品的代价;k代表是否使用折扣
struct node {
int nxt,to;
}edge[N];
void addEdge(int u,int v) {
edge[++cnt]=(node){head[u],v};
head[u]=cnt;
return;
}
void Read() {
scanf("%d%d%d%d",&n,&b,&c[1],&d[1]);
for(int i=2;i<=n;i++) {
scanf("%d%d%d",&c[i],&d[i],&x[i]);
addEdge(x[i],i);
}
return;
}
void Init() {
memset(f,0x3f,sizeof(f));
return;
}
void DP(int x) {
siz[x]=1;
f[x][0][0]=0;
f[x][1][0]=c[x];
f[x][1][1]=c[x]-d[x];
for(int i=head[x];i;i=edge[i].nxt) {
int v=edge[i].to;
DP(v);
for(int i=siz[x];i>=0;i--) {
for(int j=0;j<=siz[v];j++) {
//状态转移方程
f[x][i+j][0]=min(f[x][i+j][0],f[x][i][0]+f[v][j][0]);
f[x][i+j][1]=min(f[x][i+j][1],f[x][i][1]+f[v][j][0]);
f[x][i+j][1]=min(f[x][i+j][1],f[x][i][1]+f[v][j][1]);
}
}
siz[x]+=siz[v];
}
return;
}
void Print() {
for(int i=n;i>=1;i--) {
if(f[1][i][0]<=b||f[1][i][1]<=b) {
ans=i;
break;
}
}
printf("%d",ans);
return;
}
int main()
{
Read();
Init();
DP(1);
Print();
return 0;
}