zoukankan      html  css  js  c++  java
  • 洛谷 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

  • 相关阅读:
    Android 设置图片 Bitmap任意透明度
    Android 设置图片透明度
    Android 设置alpha值来制作透明与渐变效果的实例
    Android 仿微信朋友圈点击图片变暗
    Android 关于录音文件的编解码 实现米聊 微信一类的录音上传的功能
    Android 三种方式实现自定义圆形进度条ProgressBar
    nslookup命令
    netstat命令
    tracert与pathping
    ping命令
  • 原文地址:https://www.cnblogs.com/butterflydew/p/9042024.html
Copyright © 2011-2022 走看看