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