【HAOI2015】T1树形Dp

来源:互联网 发布:网络拒绝接入如何破解 编辑:程序博客网 时间:2024/05/18 20:12

题意:一棵树,在里面选出k个黑点,剩下的点是白点,使白点之间两两距离之和和黑点之间两两距离之和的和最大
分析:还是图样..一开始想到了点分治,可是树的分治一般处理的是关于树的路径的问题。
后来发现这道题的k明显是一种“资源”,又想到用树上的背包来搞,可是没做出,一直在想两两之间的距离怎么转移… :(
状态显然,F[i][j]为以i为根的子树,选出j个黑点的最大值,考虑这玩意儿是否能转移
如果硬要找到两两之间的距离来求的话,明显不行。
考虑到路径是由边组成的,可否用边的贡献来求解呢?还是有问题,如果我们计算一条边在i子树里的贡献来转移的话,很显然转移不完,因为还有子树外面的点会用到这条边。
其实还是对树形dp的理解不到位,树形dp的状态可以是针对整个图的,即是本棵子树在这个状态之下对答案的一个贡献,用对答案的贡献的最优来更新的话,答案就是最优。

F[x][j] = max{f[x][j-k] + f[y][k] + 本条边在全图中的贡献}
Ps: 树上背包的范围要注意!0可能要取,上界可能要取min
还有个问题:为什么k不能逆向枚举?
这个问题,真的很难发现:
在我们每次更新f[x][j]时,其实是用f[x’][j-k]来更新的
可是注意到一种情况:k可以等于0
那么这一次更新中f[x][j]是用的f[x][j-0]更新,又因为倒序枚举
f[x][j]已经被更新过了,因此,出现了自己更新自己的情况,答案偏大。
所以:当k的范围可以取到j或者0时,一定要注意!
(k=j时也会有这样的情况,如果正向枚举,k是分配给根的资源数量的话,那么k = j会是这一个循环中的最后一次更新,而f[x][j]也不是f[x’][j]了,f[x][j] = f[x][k(j)] + …就错啦)

#include<vector>#include<cstring>#include<iostream>#include<algorithm>using namespace std;typedef int _int;#define int long longconst int Lim = 2005;int n , tot_black;int siz[Lim] , f[Lim][Lim]; vector<int> edge[Lim] , value[Lim];void Dp(int x , int fa){    f[x][0] = f[x][1] = 0;    siz[x] = 1;     for(int i=0,y ; i<(int) edge[x].size() ; i++)        if( (y=edge[x][i]) != fa)        {            Dp(y , x);            siz[x] += siz[y];            int v = value[x][i];            for(int j=min(siz[x],tot_black) ; j>=0; j--)                for(int k=0 ; k<=min(j,siz[y]) ; k++)                {                    int t1 = v * k * (tot_black - k); //黑点的贡献                     int t2 = v * (siz[y] - k) * (n - siz[y] - tot_black + k);                    f[x][j] = max(f[x][j] , f[y][k] + f[x][j-k] + t1 + t2);                }        }}_int main(){    memset(f , -0x3f , sizeof f);    scanf("%lld %lld",&n,&tot_black);    for(int i=1,x,y,z;i<n;i++)    {        scanf("%lld %lld %lld",&x,&y,&z);        edge[x].push_back(y);        value[x].push_back(z);        edge[y].push_back(x);        value[y].push_back(z);      }    Dp(1 , 0);    printf("%lld",f[1][tot_black]);     return 0;}
原创粉丝点击