【题解】[IOI2005]Riv 河流

题目戳我

( ext{Solution:})

考虑显然树形(dp).

1.设(dp[i][j])表示子树(i)选了(j)个的最小代价。

发现转移时的(cost)没法转移。

2.设(dp[i][j][k])表示子树(i)选了(k)个,距离(i)最近的伐木场是(j)的最小代价。

不方便处理(i)是不是被选中的情况。

3.设(dp[i][j][k][0/1])表示的是(i)是不是建立的伐木场,其他对应第三种情况。

考虑转移:

首先,计算两点距离可以树上差分,由于我们这里要计算的两点距离都在一条链上,所以(dis=disroot_i-disroot_j.)

对于(i)不选:枚举当前已经选择了多少个,此时对于最近节点(j,vin son_i)的最近节点也必然为(j.)所以要从(dp[v][j]..)状态转移。

对于(i)选:这里(v)最近的就是(x)了。

考虑对当前节点(dp)完后的处理过程:将某些(dp)状态完全计算掉,对于需要把木头运到(j)的要把代价计算上。

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e3+10;
int n,k,tot,head[MAXN],ans;
struct E{int nxt,to,dis,v;}e[MAXN];
inline void add(int x,int y,int w){
	e[++tot]=(E){head[x],y,w};
	head[x]=tot;
}
int dp[102][102][51][2],wd[5001];
int dis[500],pa[500],cnt,ac[1001];
void dfs(int x,int fa){
	pa[x]=fa;
	for(int i=head[x];i;i=e[i].nxt){
		int j=e[i].to;
		if(j==fa)continue;
		dis[j]=dis[x]+e[i].dis;
		dfs(j,x);
	}
}
//dis[i]表示1-i的距离 预处理dis&pa 
void DP(int x){
	ac[++cnt]=x;
	//dp[i][j][k][0/1] means that the pos i is or not built a base,the nearest pos to i is j,i's tree has been build k
	//意味着i及其子树一共建立了k个,距离i最近的是j,i是不是建立了的最小代价 
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].to;
		if(v==pa[x])continue;
		DP(v);
		for(int j=tot;j>=1;--j){
			int fa=ac[j];
			//枚举所有祖先 
			for(int u=k;u>=0;--u){
				//注意倒序更新,因为要从旧状态转移 
				dp[x][fa][u][0]+=dp[v][fa][0][0];
				//当前点没有建立,所以v最近的也是fa
				//这里第三维是0 单独处理 即v及其子树没有选 
				dp[x][fa][u][1]+=dp[v][x][0][0];
				//同理 x选了 v最近的就是x了 
				for(int l=u;l>=0;--l){
					dp[x][fa][u][0]=min(dp[x][fa][u][0],dp[v][fa][l][0]+dp[x][fa][u-l][0]);
					//对于当前x没选的,那v最近的还是fa,这里dp[x][fa][u-l][0]是v子树之前x选了u-l个 
					dp[x][fa][u][1]=min(dp[x][fa][u][1],dp[v][x][l][0]+dp[x][fa][u-l][1]);
					//对于当前x选;1的,其实就是v最近的改成x,以及记住选择了x即可 
					//考虑枚举选择了几个 
				}
			}
		}
	}
	for(int i=1;i<=tot;++i){
		int fa=ac[i];
		for(int j=k;j>=0;--j){
			if(j>=1)dp[x][fa][j][0]=min(dp[x][fa][j][0]+wd[x]*(dis[x]-dis[fa]),dp[x][fa][j-1][1]);
			else dp[x][fa][j][0]+=wd[x]*(dis[x]-dis[fa]);
			//注意合并状态,对于i没建立的要把木头移到j上,建立了的就不需要,但是注意第三维的限制 
		}
	}
	cnt--;//当前点已经不会再成为祖先了 
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=2;i<=n+1;++i){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		y++;wd[i]=x;//记木材 
		add(i,y,z);add(y,i,z);
	}
	dfs(1,0);DP(1);
	printf("%d
",dp[1][1][k][0]);
	return 0;
} 
原文地址:https://www.cnblogs.com/h-lka/p/13888360.html