题解 P6223 【 [COCI 2009] PODJELA】

· · 题解

很典型的一道树形DP

首先简化一下题目:每个人有 x- v_{i}块钱(可为负)。要求出使所有人的钱数都 ≥0

DP状态确立

dp_{i,j} 为在节点为 i 的子树内进行 j 次操作,使除 i 号节点外,i 的所有子节点的钱数都 ≥0 时(符合要求),i 号节点最多能有的钱数(若为负数,则意为还需要父节点给自己的钱数)。

DP转移式

对于每一个节点,每添加一颗子树时,

```cpp next[pos][j+k+1]=max(next[pos][j+k+1],dp[pos][j]+dp[c[pos][i]][k]) ``` 其中 $pos$ 是该节点, $c[pos][i]$ 表示正在添加的子树。 $(2)$当 $dp[c[pos][i]][k]$ $≥0$ 时,可以不将子节点的钱向上运: ```cpp if(dp[c[pos][i]][k]>=0){ next[pos][k+j]=max(next[pos][k+j],dp[pos][j]); } ``` ### 附上丑陋的代码(仅删去头文件部分) ```cpp #define F(i,a,b) for(register int i=a,i##end=b;i<=i##end;++i) using namespace std; int read() { int x=0; bool fu=0; char ch=0; while(ch>'9'||ch<'0') { ch=getchar(); if(ch=='-')fu=1; } while(ch<='9'&&ch>='0') x=(x*10+ch-48),ch=getchar(); return fu?-x:x; } int n,t1,t2,x,v[2002],f[2002],dp[2002][2002],size[2002],next[2002][2002]; bool vis[2002]; vector<int>g[2002],c[2002]; void make_tree(int pos) { vis[pos]=1; if(g[pos].size()<=1 && pos!=1) { size[pos]=1; return; } size[pos]=1; F(i,0,g[pos].size()-1) { if(!vis[g[pos][i]]) { c[pos].push_back(g[pos][i]); f[g[pos][i]]=pos; make_tree(g[pos][i]); size[pos]+=size[g[pos][i]]; } } } void DP(int pos) { int ssize=0; F(i,0,c[pos].size()-1) { DP(c[pos][i]); } F(i,0,c[pos].size()-1) { F(j,0,2001) { next[pos][j]=(-INT_MAX)/3; } F(j,0,ssize) { F(k,0,size[c[pos][i]]-1) { next[pos][j+k+1]=max(next[pos][j+k+1],dp[pos][j]+dp[c[pos][i]][k]); if(dp[c[pos][i]][k]>=0) { next[pos][k+j]=max(next[pos][k+j],dp[pos][j]); } } } F(j,0,2000) { dp[pos][j]=next[pos][j]; } ssize+=size[c[pos][i]]; } } int main() { memset(dp,-0x1f,sizeof(dp)); cin>>n>>x; F(i,1,n) { v[i]=x-read(); dp[i][0]=v[i]; } F(i,2,n) { t1=read(),t2=read(); g[t1].push_back(t2); g[t2].push_back(t1); } make_tree(1);//建树 DP(1);//DP F(i,0,2000) {//输出答案 if(dp[1][i]>=0) { cout<<i; return 0; } } return 0; } ``` ~~记得双击,么么哒(~~