树DP 树上染色

[HAOI2015]树上染色
时间限制:1 s 内存限制:256 MB
【题目描述】
有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。问收益最大值是多少。
【输入格式】
第一行两个整数N,K。
接下来N-1行每行三个正整数fr,to,dis,表示该树中存在一条长度为dis的边(fr,to)。输入保证所有点之间是联通的。
【输出格式】
输出一个正整数,表示收益的最大值。
【输入样例1】
3 1
1 2 1
1 3 2
【输出样例1】
3
【数据范围】

对于30%的数据,N<=20
对于50%的数据,N<=100
对于100%的数据,N<=2000,0<=K<=N

明显是树DP,但如果去考虑哪个点是黑点就会很不方便,而任意两点间的距离过的边是确定的,那么可以考虑处理边,考虑边对答案的贡献。对于某条边,就是l=(他左侧黑点×他右侧黑点+他左侧白点×他右侧白点)×边权。
那么考虑转移,我们来挨个向答案中添加子树,枚举当前子树中有i个黑点,已枚举完的子树中共有j个黑点。
g[i+j]=max f[x][j]+f[son][i]+边权×l;
之前的所有子树中选j个+当前子树中选i个+贡献
必须单开一个数组g存当前的,因为还要用的之前的f[x][],所以不能马上转移。
看似是N^3,实际上枚举不用到M,只要到子树的大小即可。所以是N^2

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,e,adj[2005],size[2005];
ll f[2005][2005],g[2005]; 
struct road{int v,next,l;}lu[2005*2];
inline void add(int u,int v,int l){lu[++e]=(road){v,adj[u],l};adj[u]=e;}
inline void dp(int x,int fa)
{
    size[x]=1;
    for(int i=adj[x];i;i=lu[i].next)
    {
        int to=lu[i].v;
        if(to==fa)continue;
        dp(to,x);
        int x1=min(size[x],m),x2=min(size[to],m);
        for(int j=0;j<=x1;j++)
            for(int k=0;k<=x2;k++)
                if(j+k<=m)
                {
                    ll l=(m-k)*k+(size[to]-k)*(n-m-(size[to]-k));
                    g[k+j]=max(g[k+j],f[x][j]+f[to][k]+l*lu[i].l);
                }
        for(int j=0;j<=m;j++)f[x][j]=g[j],g[j]=0;
        size[x]+=size[to];
    }
}
int main()
{
    freopen("haoi2015_t1.in","r",stdin);
    freopen("haoi2015_t1.out","w",stdout);
    cin>>n>>m;int x,y,z;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);add(y,x,z);
    }
    dp(1,0);
    cout<<f[1][m];
}
原文地址:https://www.cnblogs.com/QTY2001/p/7632682.html