bzoj 4987 Tree

Written with StackEdit.

Description

从前有棵树。

找出\(K\)个点\(A_1,A_2,…,A_K\)

使得\(∑dis(A_i,A_{i+1}),(1<=i<=K-1)\)最小。

Input

第一行两个正整数\(n,k\),表示数的顶点数和需要选出的点个数。

接下来\(n-1\)行每行3个非负整数\(x,y,z\),表示从存在一条从\(x\)\(y\)权值为\(z\)的边。

\(1<=k<=n\)

\(1<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

Solution

  • 注意到选择的点集一定是两两相邻的.
  • 那么选出的点中,只有一条链可以只经过一次,其余的需要经过两次.
  • 考虑树形背包选边,令\(f(i,j,k)\)表示在子树\(i\)中选出\(j\)条边的最小代价,
    • \(k=0\):回到根节点.
    • \(k=1\):不回到根节点.
    • \(k=2\):回到根节点后再下去.
#include<bits/stdc++.h>
using namespace std;
typedef long long LoveLive;
inline int read()
{
	int out=0,fh=1;
	char jp=getchar();
	while ((jp>'9'||jp<'0')&&jp!='-')
		jp=getchar();
	if (jp=='-')
		{
			fh=-1;
			jp=getchar();
		}
	while (jp>='0'&&jp<='9')
		{
			out=out*10+jp-'0';
			jp=getchar();
		}
	return out*fh;
}
const int MAXN=3e3+10;
int cnt=0,head[MAXN];
int nx[MAXN<<1],to[MAXN<<1],val[MAXN<<1];
inline void add(int u,int v,int w)
{
	++cnt;
	nx[cnt]=head[u];
	to[cnt]=v;
	val[cnt]=w;
	head[u]=cnt;
}
int f[MAXN][MAXN][3],siz[MAXN];
inline void upd(int &x,int y)
{
	if(x>y)
		x=y;
}
void dfs(int u,int fa)
{
	siz[u]=1;
	f[u][0][0]=f[u][0][1]=0;
	for(int i=head[u];i;i=nx[i])
		{
			int v=to[i];
			if(v==fa)
				continue;
			dfs(v,u);
			for(int j=siz[u]-1;j>=0;--j)
				for(int k=siz[v]-1;k>=0;--k)
					for(int l=2;l>=0;--l)
						for(int m=l;m>=0;--m)
							upd(f[u][j+k+1][l],f[u][j][l-m]+f[v][k][m]+val[i]*(2-(m & 1)));
			siz[u]+=siz[v];
		}
}
int n,m;
int main()
{
	n=read(),m=read();
	for(int i=1;i<n;++i)
		{
			int u=read(),v=read(),w=read();
			add(u,v,w);
			add(v,u,w);
		}
	memset(f,0x3f,sizeof f);
	dfs(1,0);
	int ans=0x7fffffff;
	for(int i=1;i<=n;++i)
		for(int j=0;j<=2;++j)
			ans=min(ans,f[i][m-1][j]);
	printf("%d\n",ans);
	return 0;
}

参考了dalao的blog.

posted @ 2018-11-28 17:21  jklover  阅读(58)  评论(0编辑  收藏