[IOI2005]Riv 河流

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

posted @ 2021-03-30 15:53  Troverld  阅读(83)  评论(6编辑  收藏  举报