BZOJ P4033 树上染色【树形DP+背包】

题目分析(以下的M都是题目当中输入的K):

树形DP(好吧这是废话)。我们按照树形DP的套路(啥?你不知道树形DP的套路?)很容易得出这道题的状态设定:

DP[I][J]表示以I为根的子树当中涂了J个黑点的最大收益,J<=M

那么问题来了,我们应该如何进行状态转移,关键点在于应该如何处理状态转移时发生的收益变化?

让我们来考虑这样一个问题:

树上一条边Edge(X,Y),不妨设X为Y的父亲节点,我们假设不在以X为根的子树外(包括X点)一共有P个黑点,那么在以X为根的子树内也就是Y内(不包括X点,包括Y点)一共有M-P个黑点。关键点来了,根据乘法原理所有的黑点连边那么一共有P*(M-P)条边,这P*(K-P)条边都经过同一条边X->Y,白点同此。这样我们就将需要计算的距离转化为了某一条边的计算,这样就方便了我们的转移。

于是就可以得到下面的状态转移方程了(其实是一个树上背包的过程):

Size[X]表示以X为根节点的子树的大小,
DP[X][J]=max{DP[X][J],DP[X][J-K]+DP[Y][K]+K*(J-K)*(X->Y)+(Size[Y]-K)*(N-M-Size[Y]+K)*(X->Y)}

由于在这棵树当中哪一个点是根节点都是相对的,为了方便讨论与写代码,都以1号点为根节点,所以最后的答案为DP[1][M]。

另,这道题似乎卡时,大家就不要在不必要的地方开long long 了

于是现在代码就很好写啦,关键代码:

void Tree_DP(int X,int Fx){
    int I,J,K;Size[X]=1;
    for(I=Head[X];I;I=Next[I]){
        int Y=To[I];
        if(Y!=Fx){
            Tree_DP(Y,X);
            Size[X]+=Size[Y];
        }
    }
    for(I=0;I<=Size[X];I++){
        DP[X][I]=-1ll;
    }DP[X][0]=DP[X][1]=0ll;
    for(I=Head[X];I;I=Next[I]){
        int Y=To[I];LL Z=Edge[I];
        if(Y!=Fx){
            int NumX=min(M,Size[X]);
            for(J=NumX;J>=0;J--){
                int NumY=min(J,Size[Y]);
                for(K=0;K<=min(NumY,J);K++){
                    if(DP[X][J-K]!=-1ll){
                        DP[X][J]=max(DP[X][J],DP[X][J-K]+DP[Y][K]+(LL)K*(M-K)*Z+(LL)(Size[Y]-K)*(N-M-Size[Y]+K)*Z);
                    }
                }
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/yanzhenhuai/article/details/80718187