题意

给一棵\(n\)个点的边带权的树,要求选\(k\)个点染成白色,其他点为黑色,最大化(黑点两两距离+白点两两距离),\((n\leq 2000)\)

思路

一道好题,思路不容易get,但是想出来之后很简单

一般树形DP的套路:设\(f_{rt,i}\)表示以\(rt\)为根的子树选\(i\)个点染成白色,这棵子树白点两两距离和;然后发现不会转移了qwq

为什么这样做不行呢?,也就是说这棵子树中的白点需要和子树外的白点统计距离,但是状态的定义使得我们只能统计子树内的白点两两距离,合并状态的时候由于不知道白点的分布而不能求子树间白点的贡献


考虑边的贡献,即可完美解决这个问题

一条边的贡献为:\(wi \times\) \((\) 一边的白点数\(\times\)另一边的白点数\(+\)一边的黑点数\(\times\)另一边的黑点数\()\),于是设\(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;
}
12-28 10:29