题意
给一棵(n)个点的边带权的树,要求选(k)个点染成白色,其他点为黑色,最大化(黑点两两距离+白点两两距离),((nleq 2000))
思路
一道好题,思路不容易get,但是想出来之后很简单
一般树形DP的套路:设(f_{rt,i})表示以(rt)为根的子树选(i)个点染成白色,这棵子树中白点两两距离和;然后发现不会转移了qwq
为什么这样做不行呢?因为这棵子树白点的贡献并不仅限于这棵子树,也就是说这棵子树中的白点需要和子树外的白点统计距离,但是状态的定义使得我们只能统计子树内的白点两两距离,合并状态的时候由于不知道白点的分布而不能求子树间白点的贡献
考虑边的贡献,即可完美解决这个问题
一条边的贡献为:(wi imes) (() 一边的白点数( imes)另一边的白点数(+)一边的黑点数( imes)另一边的黑点数()),于是设(f_{rt,i})表示以(rt)为根的子树中选择(i)个点染为白色时,这棵子树中所有边的贡献
可以发现转移就是个树形背包,于是就这么搞就完事了
Code
#include<bits/stdc++.h>
#define N 2005
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
typedef long long ll;
int n,m,size[N];
ll f[N][N];
struct Edge
{
int next,to;
ll dis;
}edge[N<<1];int head[N],cnt=1;
void add_edge(int from,int to,ll dis)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
edge[cnt].dis=dis;
head[from]=cnt;
}
template <class T>
void read(T &x)
{
char c; int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=(x<<1)+(x<<3)+c-48; x*=sign;
}
void dfs(int rt,int fa)
{
size[rt]=1;
f[rt][0]=f[rt][1]=0;
for(int i=head[rt];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs(v,rt);
size[rt]+=size[v];
for(int j=Min(size[rt],m);j>=0;--j)
{
for(int k=0;k<=Min(j,size[v]);++k)
{
ll val = edge[i].dis*((ll)k*(m-k) + (ll)(size[v]-k)*(n-m-size[v]+k));
f[rt][j]=Max(f[rt][j] , f[v][k] + f[rt][j-k] + val);
}
}
}
}
int main()
{
read(n);read(m);
for(int i=1;i<n;++i)
{
int x,y; ll z;
read(x);read(y);read(z);
add_edge(x,y,z);
add_edge(y,x,z);
}
memset(f,-50,sizeof(f));
dfs(1,0);
cout<<f[1][m]<<endl;
return 0;
}