BZOJ_4987_Tree_树形DP

BZOJ_4987_Tree_树形DP

Description

从前有棵树。
找出K个点A1,A2,…,Ak。
使得∑dis(AiAi+1),(1<=i<=K-1)最小。

Input

第一行两个正整数n,k,表示数的顶点数和需要选出的点个数。
接下来n-l行每行3个非负整数x,y,z,表示从存在一条从x到y权值为z的边。
I<=k<=n。
l<x,y<=n
1<=z<=10^5
n <= 3000

Output

一行一个整数,表示最小的距离和。

Sample Input

10 7
1 2 35129
2 3 42976
3 4 24497
2 5 83165
1 6 4748
5 7 38311
4 8 70052
3 9 3561
8 10 80238

Sample Output

184524

考场上写了个贪心的树形背包水了50分。
说真的这状态其实好简单的不知道为什么没想到。
可以发现选的一定是一个连通块。
在一个连通块内的走法肯定是沿着直径的一个端点走向另一个端点,中间经过剩余的点。
这样代价是总边权*2-直径长度。
设f[i][j][k]表示i的子树内选了j个点,其中子树里有k个点是直径的端点(k<=2)。
转移的话很简单,考虑父亲到儿子连的这条边对答案贡献几次即可。
 
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
#define N 3050
#define _min(x,y) ((x)<(y)?(x):(y))
int head[N],to[N<<1],nxt[N<<1],val[N<<1],n,cnt,K,f[N][N][3],a[N];
int ans=1<<30,siz[N];
inline void add(int u,int v,int w) {
	to[++cnt]=v; nxt[cnt]=head[u]; head[u]=cnt; val[cnt]=w;
}
void upd(int &x,int y) {if(x>y) x=y;}
void dp(int x,int y) {
	int i,j,k; siz[x]=1;
	f[x][1][0]=f[x][1][1]=0;
	for(i=head[x];i;i=nxt[i]) {
		if(to[i]!=y) {
			dp(to[i],x);
			for(j=siz[x];j;j--) {
				for(k=siz[to[i]];k;k--) {
					int w=val[i],w2=w<<1;
					upd(f[x][j+k][0],f[x][j][0]+f[to[i]][k][0]+w2);
					upd(f[x][j+k][1],f[x][j][0]+f[to[i]][k][1]+w);
					upd(f[x][j+k][1],f[x][j][1]+f[to[i]][k][0]+w2);
					upd(f[x][j+k][2],f[x][j][0]+f[to[i]][k][2]+w2);
					upd(f[x][j+k][2],f[x][j][1]+f[to[i]][k][1]+w);
					upd(f[x][j+k][2],f[x][j][2]+f[to[i]][k][0]+w2);
				}
			}
			siz[x]+=siz[to[i]];
		}
	}
	ans=min(ans,f[x][K][2]);
}
int main() {
	memset(f,0x3f,sizeof(f));
	scanf("%d%d",&n,&K);
	int i,x,y,z;
	for(i=1;i<n;i++) {
		scanf("%d%d%d",&x,&y,&z); add(x,y,z); add(y,x,z);
	}
	dp(1,0);
	printf("%d\n",ans);
}

 

posted @ 2018-08-12 18:28  fcwww  阅读(330)  评论(0编辑  收藏  举报