[HAOI2015]树上染色

按点来算贡献的话并不好算。考虑到路径长度和可以拆分成每条边的长度乘上其被经过的次数,所以统计边的贡献就可以了。树上一条边会将树分成两边,而该边被经过的次数实质上就是两边的白点个数之积加上黑点个数积。因为定了根,所以其中一边可以按子树考虑,于是就转换成了树形dp。

#include<stdio.h>
#define ll long long
#define N 2007

struct E{
    int next,to;
    ll dis;
}e[N<<1];
int head[N],cnt=0,n,m;
ll dp[N][N],sz[N];

inline void add(int id,int to,ll dis){
    e[++cnt]=(E){head[id],to,dis};
    head[id]=cnt;
}

inline int min(int x,int y){return x<y? x:y;}
inline ll max(ll x,ll y){return x>y? x:y;}
void dfs(int u,int fa){
    sz[u]=1;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa) continue;
        dfs(v,u);
        for(int j=sz[u];~j;j--)
            for(int k=sz[v];~k;k--)
                dp[u][j+k]=max(dp[u][j+k],dp[u][j]+dp[v][k]+e[i].dis*(1LL*k*(m-k)+1LL*(sz[v]-k)*(n-m-sz[v]+k)));
        sz[u]+=sz[v];
    }
}

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

Tips

之前dp转移的时候是正序枚举的,然后WA了。看了很多题解之后,也并没有弄清,实际上很多人自己都没有弄清楚就在乱写题解。这道题的 (j)(k) 都要倒序枚举,回忆做 01 背包的时候也要倒序枚举,是因为有一维状态被滚掉了。会看这道题,也有一维被滚掉了,本来的状态应该是 (dp[u][i][j+k]) ,表示在子树 (u) 中的前 (i) 棵子树中选 (j+k) 个白点的最大贡献,而更新时用的是 (dp[u][i-1][j+k])(dp[u][i-1][j]) 。删掉 (i) 这一维,就要保证 (j+k) 要在 (j) 之前更新,那么 (j)(k) 都要倒序就很显然了。

原文地址:https://www.cnblogs.com/wwlwQWQ/p/14123859.html