[BZOJ 4033] 树上染色
Link:
Solution:
此题用到了计算贡献的方法,
将 多条路径的路径和 $->$ $\sum_{i=1}^{n-1} w[i]*cnt[i]$
这样我们由找出所有路径再计算转化成了对每条边计算其的贡献
由于所有节点只用2种选择,接下来就是比较套路的树形DP了
设 $dp[i][j]$ 为在以 $i$ 为根的子树中,有$j$个黑点时的$MAX$。
这样按照$dfs$序依次处理每个节点$x$,对子树背包$DP$,最后再加上$w_{<x,father[x]>}$的贡献即可
Code:
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> P; const int MAXN=2005*2; vector<P> G[MAXN]; ll n,k,dp[MAXN][MAXN],sz[MAXN]; void tree_dp(int x,int anc,ll val) { sz[x]=1; for(int i=0;i<G[x].size();i++) { int v=G[x][i].first; if(v==anc) continue; tree_dp(v,x,G[x][i].second); for(int p=min(sz[x],k);p>=0;p--) for(int q=min(sz[v],k-p);q>=0;q--) dp[x][p+q]=max(dp[x][p+q],dp[x][p]+dp[v][q]); sz[x]+=sz[v]; } for(int i=0;i<=min(sz[x],k);i++) //统计贡献 dp[x][i]+=val*((ll)i*(k-i)+(sz[x]-i)*((n-sz[x])-(k-i))); } int main() { scanf("%lld%lld",&n,&k); for(int i=1;i<n;i++) { ll x,y,z;scanf("%lld%lld%lld",&x,&y,&z); G[x].push_back(P(y,z)); G[y].push_back(P(x,z)); } tree_dp(1,0,0); printf("%lld",dp[1][k]); return 0; }
Review:
1、计算贡献的思想:
将 多个整体 拆分成 每个个体$*$出现次数的和
只要能快速地计算出贡献,依次考虑每个个体即可
2、树形$DP$的套路和注意事项:
套路:树形$DP$大部分时候都是依据$dfs$序在对子树背包$DP$
Note:(1)背包$DP$要从后往前更新,防止多次计算
(2)一般$size[x]$的更新都要放在对该子树更新完之后
3、点集的划分
该题的特殊性在于对点集严格划分为2堆,才能直接算出每种点的个数
当出现对点集严格划分的题目时,只要保存一个量,并考虑能否计算贡献