XL.[IOI2005]Riv 河流
新转移方式get~~~
我必须吐槽一下现在赞最多的那篇题解,虽然思路巧妙,但是明显没有“物尽其用”,对于各DP数组的真实含义也没有把握清楚。
一个naive的想法就是:设\(f[i][j]\)表示:在\(i\)的子树中,修了\(j\)个场子,的最小费用。
但是这样不是很好转移——子树传上来的信息不能直接合并,因为我们必须知道场子到底修哪了才能准确得出答案。
而我们又不可能在状态里面维护这么多场子——状压不了。
等等,我们为什么要从根记录子树,为什么不是从子树记录根?
我们设\(f[x][j][k]\)表示:
以\(x\)为根的子树中,修了\(k\)个堡。并且,强制在第\(j\)个点修个堡(\(j\)是\(i\)的祖先)。
这样,合并子树时就可以直接背包了——因为每个节点流到的堡确定了,代价自然就可以提前算出。
即:
\(f[x][j][k]=\max\{f[x][j][l]+f[y][j][k-l]\},\text{y is a son of x}\)
每次将\(x\)的状态同\(y\)合并。
但这样就会出现一些问题——我们说要在\(j\)修个堡,但是这只是空头支票,没有算到\(k\)里面,当访问到\(j\)时,这个债是要还的!
因此对于\(f[x][x][k]\),我们应该让\(k\)全体右移一位,即\(f[x][x][k]=f[x][x][k-1]\),且\(f[x][x][0]=\infty\)(欠的一个堡还不回来,只能破产)。
还有,我们要计算\(x\)位置新产生的代价。这个代价要么\(x\)位置额外再修一个堡,要么就是到\(j\)的距离。因此我们有
\(f[x][j][k]=\min\Big(f[x][j][k]+val_x*(dis_x-dis_k),f[x][x][k]\Big)\)
则答案为\(f[0][0][K+1]\)(\(0\)号点有个免费的堡)。
代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,head[110],cnt,f[110][110][60],g[60],val[110],dis[110],anc[110],tp;
struct node{
int to,next,val;
}edge[210];
void ae(int u,int v,int w){
edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
}
void dfs(int x){
anc[++tp]=x;
for(int i=head[x],y;i!=-1;i=edge[i].next){
y=edge[i].to,dis[y]=dis[x]+edge[i].val,dfs(y);
for(int j=1;j<=tp;j++){
for(int k=0;k<=m;k++)g[k]=0x3f3f3f3f;
for(int k=0;k<=m;k++)for(int l=0;l<=k;l++)g[k]=min(g[k],f[x][anc[j]][k-l]+f[y][anc[j]][l]);
for(int k=0;k<=m;k++)f[x][anc[j]][k]=g[k];
}
}
for(int j=m;j;j--)f[x][x][j]=f[x][x][j-1];
f[x][x][0]=0x3f3f3f3f;
for(int j=1;j<tp;j++)for(int k=0;k<=m;k++)f[x][anc[j]][k]+=val[x]*(dis[x]-dis[anc[j]]),f[x][anc[j]][k]=min(f[x][anc[j]][k],f[x][x][k]);
tp--;
}
int main(){
scanf("%d%d",&n,&m),m++,memset(head,-1,sizeof(head));
for(int i=1,x,y;i<=n;i++)scanf("%d%d%d",&val[i],&x,&y),ae(x,i,y);
dfs(0);
// for(int i=1;i<=n;i++)printf("%d ",dis[i]*val[i]);puts("");
printf("%d\n",f[0][0][m]);
return 0;
}