[2019.2.28]BZOJ4033 [HAOI2015]树上染色

博客围绕树形DP展开,设\(dp_{i,j}\)表示\(i\)子树中有\(j\)个黑色节点的最大边权和,分析合并节点\(x\)和子节点\(y\)时边对答案的贡献,给出状态转移方程。原本认为单次合并复杂度为\(O(n^2)\),总复杂度\(O(n^3)\),实际似乎是\(O(n^2)\),指出树形DP复杂度较玄学。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先我们设\(dp_{i,j}\)表示\(i\)和的子树中,有\(j\)个黑色节点的最大边权和。

我们设\(i\)当前已合并的子树大小为\(sz_i\)

现在我们要合并节点\(x\)和它的子节点\(y\)

我们考虑\(x\)\(y\)之间的边对答案的贡献。

这个贡献就是这条边[(一侧的黑点数\(\times\)另一侧的黑点数)+(一侧的白点数\(\times\)另一侧的白点数)]\(\times\)边权。

为什么呢?

显然对于任意位于这条边两侧的同色点,它们之间的路径必然经过这条边。

我们设这次合并之前的\(dp_x\)\(ldp\),这条边边权为\(ev\),那么有

\(dp_{x,i}=max\{dp_{y,j}+ldp_{i-j}+j\times(k-j)\times ev+(sz_y-j)\times(n-k-sz_y+j)\times ev\}\)

但是这样看起来单次合并是\(O(n^2)\)的,总时间复杂度就是\(O(n^3)\)的。

一开始我也是这么认为的。

一交发现过了,而且跑的很快。

它实际上似乎是\(O(n^2)\)的。

为什么呢?

树形dp复杂度太玄学了

code:

#include<bits/stdc++.h>
#define Add(l,r,val) (c[l]+=val,c[r+1]-=val)
#define Sum(l,r) (sum[r]-(l?sum[l-1]:0))
using namespace std;
struct edge{
    int t,v,nxt;
}e[4010];
int n,k,u,v,w,cnt,be[2010],sz[2010];
long long dp[2010][2010],cpy[2010],ans;
void add(int x,int y,int val){
    e[++cnt].t=y,e[cnt].v=val,e[cnt].nxt=be[x],be[x]=cnt;
}
void Merge(int x,int y,int ev){
    for(int i=0;i<=k;++i)cpy[i]=dp[x][i],dp[x][i]=0;
    for(int i=0;i<=k&&i<=sz[y];++i)for(int j=0;i+j<=k&&j<=sz[x];++j)dp[x][i+j]=max(dp[x][i+j],dp[y][i]+cpy[j]+1ll*ev*i*(k-i)+1ll*ev*(sz[y]-i)*(n-k-sz[y]+i));
}
void TDP(int x){
    sz[x]=1;
    for(int i=be[x];i;i=e[i].nxt)!sz[e[i].t]?TDP(e[i].t),Merge(x,e[i].t,e[i].v),sz[x]+=sz[e[i].t],0:0;
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<n;++i)scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
    TDP(1);
    printf("%lld",dp[1][k]);
    return 0;
}

转载于:https://www.cnblogs.com/xryjr233/p/BZOJ4033.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值