【HAOI2015】T1树形Dp

题意:一棵树,在里面选出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
#include
#include
#include
using namespace std;
typedef int _int;
#define int long long
const 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;iscanf("%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;
}

你可能感兴趣的:(树形Dp,树)