洛谷 P3177 树上染色 解题报告

P3177 [HAOI2015]树上染色

题目描述

有一棵点数为(N)的树,树边有边权。给你一个在(0) ~ (N)之内的正整数(K),你要在这棵树中选择(K)个点,将其染成黑色,并将其他的(N-K)个点染成白色 。 将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的受益。问受益最大值是多少。

输入输出格式

输入格式:

第一行包含两个整数 (N, K) 。接下来 (N-1) 行每行三个正整数 (fr, to, dis) , 表示该树中存在一条长度为(dis)的边((fr,to)) 。输入保证所有点之间是联通的。

输出格式:

输出一个正整数,表示收益的最大值。

说明

对于(100%)的数据,(0<=K<=N <=2000)


这里阐述一下我的思考方式。

首先,真的一下子想到贪心去了。前几天做了消防【SDOI2011】,觉得在树的直径上会有一些奇妙优美的性质,遂开始想办法证明。

然后考试完了还在证贪心(emmmm)

解法很容易懂,但是很难想(我太蒻了)


正解是树形(DP)

(dp[i][j])为以(i)为根节点的子树中 在染了(j)个黑点时 所有的边再加上(i)头上的那个边 对整颗树 的答案贡献

有灵性就有灵性在这个整颗树上

在已经处理好子树(v)时,我们加入边(E_k(u,v))(u)(v)的父亲)

(E_k)的贡献即为边权(w*)子树(u)中白点个数(*)子树(u)外面的数白点个数(+w*)子树(u)中黑点个数(*)子树(u)外面的数黑点个数,这点不难想明白

当然,不做头顶上的边也是可以的,但很多树形(dp)管一下自己的脑门是比较方便的

转移时,我们对每棵子树做树上分组背包,最后在判断一下她自己头上的边


code

#include <cstdio>
#include <cstring>
#define ll long long
ll max(ll x,ll y) {return x>y?x:y;}
ll min(ll x,ll y) {return x<y?x:y;}
const int N=2001;
ll dp[N][N];
//根节点为i的子树j个点染黑所有边的最大答案
ll n,k;
struct Edge
{
    ll to,next,w;
}edge[N*2];
ll head[N],cnt=0;
void add(ll u,ll v,ll w)
{
    edge[++cnt].to=v;
    edge[cnt].next=head[u];
    edge[cnt].w=w;
    head[u]=cnt;
}
int used[N];
ll dfs(ll u,ll c)
{
    used[u]=1;
    ll size=0;
    memset(dp[u],-1,sizeof(dp[u]));
    dp[u][0]=0;
    for(ll i=head[u];i;i=edge[i].next)
    {
        ll v=edge[i].to,w=edge[i].w;
        if(!used[v])
        {
            ll si=dfs(v,w);size+=si;
            for(ll j=min(k,size);j>=0;j--)
            {
                ll r=min(j,min(si,k));
                for(ll k0=0;k0<=r;k0++)
                    if(dp[u][j-k0]!=-1)
                        dp[u][j]=max(dp[u][j],dp[u][j-k0]+dp[v][k0]);
            }
        }
    }
    size++;
    ll l=max(size+k-n,1);
    for(ll i=min(k,size);i>=l;i--)
    {
        ll cc=c*(k-i)*i+c*(n-size-k+i)*(size-i);
        dp[u][i]=max(dp[u][i],dp[u][i-1])+cc;
    }
    l=size+k-n;
    dp[u][0]+=c*(n-size-k)*size;
    return size;
}

int main()  
{
    scanf("%lld%lld",&n,&k);
    ll u,v,w;
    for(ll i=1;i<n;i++)
    {
        scanf("%lld%lld%lld",&u,&v,&w);
        add(u,v,w),add(v,u,w);
    }
    dfs(1,0);
    printf("%lld
",dp[1][k]);
    return 0;
}


细节问题:
1.分组背包转移方程为:(dp[u][j]=max(dp[u][j],dp[u][j-k0]+dp[v][k0]))
我们一定要判断(dp[u][j-k0])的合法性
2.开(long long)不要吝啬空间(除非卡的太死,反正也就两倍),能看尽量就开,只开一部分在计算时真的不注意就可能丢失了或者爆了
3.背包的上下界,最好加上,不然常数一大可能会(t)。有的不加甚至会出现一些问题。


2018.5.15

原文地址:https://www.cnblogs.com/butterflydew/p/9042024.html