您的位置:首页 > 其它

[BZOJ4033][HAOI2015]树上染色(思路+树形背包DP)

2018-01-05 21:23 573 查看
以下记sze[u]为u的子树大小,val(u,v)为边(u,v)的权值。

看到
189ad
2000的数据范围,首先猜想到可能复杂度是O(n2)。

首先想到的DP方案是:f[u][i]为u的子树内,将i个点染成黑色,在子树内的最大收益。最后答案就是f[1][m]。

然而,不难推出这个DP方案存在后效性——转移时,只有知道了子树内选出的黑色节点的深度之和,才能正确地转移。

因此换一个状态定义:f[u][i]为u的子树内,将i个点染成黑色,对答案的最大贡献。

怎么理解这个「贡献」呢?

也就是说,在这个子树,所有同色的点对的距离之和计入贡献。而如果存在一个同色的点对v和w,v在u的子树而w在u的子树,则这个点对的贡献为v到u的距离。

转移,也就是做一次树形背包DP,设当前考虑到u的子节点v,之前已经遍历过的子树推出的DP结果为f′[u][]。

那么考虑合并f′[u][i]和f[v][j]得到f[u][i+j]。可以想到,在v的子树内的j个黑点可以与v的子树外的m−j个黑点配对,同样v的子树内的sze[v]−j个白点也可以与v的子树外的n−m−sze[v]+j个白点配对。并且这些点对之间的路径都经过了边(u,v)。所以转移为:

f[u][i+j]=min(f[u][i+j],f′[u][i]+f[v][j]

+(j∗(m−j)+(sze[v]−j)∗(n−m−sze[v]+j))∗val(u,v))

答案仍然是f[1][m]。

复杂度:看上去是O(n3),但是i的上界只有sze[u],因此枚举两个子树选出的黑点个数,相当于每对点都只在lca处被计算了一次,因此复杂度O(n2)。

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
int res = 0; bool bo = 0; char c;
while (((c = getchar()) < '0' || c > '9') && c != '-');
if (c == '-') bo = 1; else res = c - 48;
while ((c = getchar()) >= '0' && c <= '9')
res = (res << 3) + (res << 1) + (c - 48);
return bo ? ~res + 1 : res;
}
typedef long long ll;
const int N = 2005, M = 4005;
int n, m, ecnt, nxt[M], adj
, go[M], val[M], sze
;
ll f

, x
;
void add_edge(int u, int v, int w) {
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}
void dfs(int u, int fu) {
int i, j; sze[u] = 1;
for (int e = adj[u], v; e; e = nxt[e]) {
if ((v = go[e]) == fu) continue; dfs(v, u);
for (i = 0; i <= sze[u] + sze[v]; i++) x[i] = 0;
for (i = 0; i <= sze[u]; i++) for (j = 0; j <= sze[v]; j++) {
if (i + j > m || n - m < sze[v] - j) continue;
ll delta = (1ll * j * (m - j) + 1ll * (sze[v] - j)
* ((n - m) - (sze[v] - j))) * val[e];
x[i + j] = max(x[i + j], f[u][i] + f[v][j] + delta);
}
for (i = 0; i <= sze[u] + sze[v]; i++) f[u][i] = x[i]; sze[u] += sze[v];
}
}
int main() {
int i, x, y, z; n = read(); m = read();
for (i = 1; i < n; i++) x = read(), y = read(),
z = read(), add_edge(x, y, z); dfs(1, 0);
cout << f[1][m] << endl;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: