[LOJ#2478][九省联考2018]林克卡特树(树形DP+带权二分)

Address

洛谷P4383
BZOJ5252
LOJ#2478

Solution

简版题意:在一棵 n n 个节点的边带权树上删掉 k k 条边再加上 k k 条权为 0 0 的边组成一棵新树,最大化新树的直径。
容易发现,如果删掉的 k k 条边给定,那么答案就是这 k + 1 k+1 个连通块的直径之和。
于是问题转化成在树上选出 k + 1 k+1 条不相交的路径的最大权值和。点视为退化的路径。
树形 DP : f [ u ] [ i ] [ 0 / 1 / 2 ] f[u][i][0/1/2] 表示 u u 的子树内选出 i i 条路径,根的度数为 0 0 1 1 2 2 的最大收益。
注:特殊情况:如果 u u 没有被选出则度数为 0 0 ,如果 u u 单独作为一条路径则度数为 1 1
转移即枚举子节点 v v 进行各种讨论,设 f f' v v 之前的 DP 数组。下面我们先不考虑 u u 单独作为一条路径的情况。
f [ u ] [ i + j ] [ s ] = max ( f [ u ] [ i + j ] [ s ] , f [ u ] [ i ] [ s ] + f [ v ] [ j ] [ t ] ) f[u][i+j][s]=\max(f[u][i+j][s],f'[u][i][s]+f[v][j][t])
其中 s s t t [ 0 , 2 ] Z [0,2]\cap\Z 内任意数。
f [ u ] [ i + j ] [ 1 ] = max ( f [ u ] [ i + j ] [ 1 ] , f [ u ] [ i ] [ 0 ] + f [ v ] [ j ] [ 1 ] + l e n ( u , v ) ) f[u][i+j][1]=\max(f[u][i+j][1],f'[u][i][0]+f[v][j][1]+len(u,v))
其中 l e n ( u , v ) len(u,v) 为边 ( u , v ) (u,v) 的权。
f [ u ] [ i + j 1 ] [ 2 ] = max ( f [ u ] [ i + j 1 ] [ 2 ] , f [ u ] [ i ] [ 1 ] + f [ v ] [ j ] [ 1 ] + l e n ( u , v ) ) f[u][i+j-1][2]=\max(f[u][i+j-1][2],f'[u][i][1]+f[v][j][1]+len(u,v))
最后算上 u u 单独作为一条路径的贡献,还是一样的树形背包。这里略去。
最后答案:
max ( f [ u ] [ k + 1 ] [ 0 ] , f [ u ] [ k + 1 ] [ 1 ] , f [ u ] [ k + 1 ] [ 2 ] ) \max(f[u][k+1][0],f[u][k+1][1],f[u][k+1][2])
复杂度 O ( n k ) O(nk) O ( n 2 ) O(n^2)
而对于包含「 k k 个」这样约束的问题,我们可以考虑使用带权二分(又称 wqs 二分、凸优化)去掉「 k k 个」的约束。
如果你有兴趣将 i 从 1 到 k+1 把 max(f[u][i][0],f[u][i][1],f[u][i][2]) 的值全部打出来, 可以发现这是一个单峰函数,在最大值左边递增,右边递减。然后如果你还有兴趣将打出来的数组进行差分, 那么又可以发现差分后的数组是单调递减的。
我们考虑给每条路径加上一个权值 m i d mid
m i d mid\rightarrow-\infty 时,最优方案是只选一条路径。
m i d + mid\rightarrow+\infty 时,最优方案是选出 n n 条只包含一个点的路径。
并且随着 m i d mid 的增加,最优方案选出的路径数单调不减。
所以我们二分 m i d mid f f 去掉第二维后转移方程变成:
f [ u ] [ s ] = max ( f [ u ] [ s ] , f [ u ] [ s ] + f [ v ] [ t ] ) f[u][s]=\max(f[u][s],f'[u][s]+f[v][t])
f [ u ] [ 1 ] = max ( f [ u ] [ 1 ] , f [ u ] [ 0 ] + f [ v ] [ 1 ] + l e n ( u , v ) ) f[u][1]=\max(f[u][1],f'[u][0]+f[v][1]+len(u,v))
f [ u ] [ 2 ] = max ( f [ u ] [ 2 ] , f [ u ] [ 1 ] + f [ v ] [ 1 ] + l e n ( u , v ) m i d ) f[u][2]=\max(f[u][2],f'[u][1]+f[v][1]+len(u,v)-mid)
枚举完 u u 所有子节点后:
f [ u ] [ 1 ] = max ( f [ u ] [ 1 ] , m i d + v s o n [ u ] max ( f [ v ] [ 0 ] , f [ v ] [ 1 ] , f [ v ] [ 2 ] ) ) f[u][1]=\max(f[u][1],mid+\sum_{v\in son[u]}\max(f[v][0],f[v][1],f[v][2]))
为了计算出最优方案使用的路径数,我们还需要记录 g [ u ] [ 0 / 1 / 2 ] g[u][0/1/2] 表示对应的 f [ u ] [ 0 / 1 / 2 ] f[u][0/1/2] 在路径权值和最大的前提下选出的路径数的最大值
假定根为 1 1 ,那么二分时如果
max { g [ 1 ] [ s ] f [ 1 ] [ s ] = max ( f [ 1 ] [ 0 ] , f [ 1 ] [ 1 ] , f [ 2 ] [ 1 ] ) } k + 1 \max\{g[1][s]|f[1][s]=\max(f[1][0],f[1][1],f[2][1])\}\ge k+1
则往左调整。否则往右调整。
如果二分之后得到的最优附加权为 Z Z ,那么再 DP 一遍,答案为:
max ( f [ 1 ] [ 0 ] , f [ 1 ] [ 1 ] , f [ 1 ] [ 2 ] ) ( k + 1 ) × Z \max(f[1][0],f[1][1],f[1][2])-(k+1)\times Z
复杂度 O ( n log V ) O(n\log V) V V 为二分范围,而实际上这个树形 DP 的常数和 V V 的实际值都较大。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Tree(u) for (int e = adj[u], v; e; e = nxt[e]) if ((v = go[e]) != fu)

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;
}

template <class T>
T Max(T a, T b) {return a > b ? a : b;}

typedef long long ll;
const int N = 3e5 + 5, M = N << 1;
const ll INF = 1e18;

int n, k, ecnt, nxt[M], adj[N], go[M], val[M], g[N][3], d[N], tg[3];
ll f[N][3], tf[3];

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;
}

bool overs(ll f, int g, ll tf, int tg)
{
	return f > tf || (f == tf && g > tg);
}

void dfs(int u, int fu, ll mid)
{
	int i, j;
	Tree(u) d[u]++, dfs(v, u, mid);
	f[u][0] = 0; f[u][1] = f[u][2] = -INF;
	g[u][0] = g[u][1] = g[u][2] = 0;
	Tree(u)
	{
		tf[0] = tf[1] = tf[2] = -INF; tg[0] = tg[1] = tg[2] = 0;
		For (i, 0, 2) For (j, 0, 2)
			if (f[u][i] != -INF && f[v][j] != -INF
				&& overs(f[u][i] + f[v][j], g[u][i] + g[v][j], tf[i], tg[i]))
					tf[i] = f[u][i] + f[v][j], tg[i] = g[u][i] + g[v][j];
		if (f[u][0] != -INF && f[v][1] != -INF
			&& overs(f[u][0] + f[v][1] + val[e], g[u][0] + g[v][1], tf[1], tg[1]))
				tf[1] = f[u][0] + f[v][1] + val[e], tg[1] = g[u][0] + g[v][1];
		if (f[u][1] != -INF && f[v][1] != -INF
			&& overs(f[u][1] + f[v][1] + val[e] - mid,
				g[u][1] + g[v][1] - 1, tf[2], tg[2]))
					tf[2] = f[u][1] + f[v][1] + val[e] - mid,
					tg[2] = g[u][1] + g[v][1] - 1;
		f[u][0] = tf[0]; f[u][1] = tf[1]; f[u][2] = tf[2];
		g[u][0] = tg[0]; g[u][1] = tg[1]; g[u][2] = tg[2];
	}
	ll sf = 0; int sg = 0;
	Tree(u)
	{
		ll af = -INF; int ag = 0;
		For (i, 0, 2) if (f[v][i] != -INF && overs(f[v][i], g[v][i], af, ag))
			af = f[v][i], ag = g[v][i];
		if (af == -INF) return;
		sf += af; sg += ag;
	}
	if (overs(sf + mid, sg + 1, f[u][1], g[u][1]))
		f[u][1] = sf + mid, g[u][1] = sg + 1;
}

int getmax()
{
	int i, sg = 0; ll sf = -INF;
	For (i, 0, 2) if (f[1][i] != -INF && overs(f[1][i], g[1][i], sf, sg))
		sf = f[1][i], sg = g[1][i];
	return sg;
}

int main()
{
	int i, x, y, z;
	n = read(); k = read();
	For (i, 1, n - 1) x = read(), y = read(), z = read(),
		add_edge(x, y, z);
	ll l = -1e13, r = 1e13;
	while (l <= r)
	{
		ll mid = l + r >> 1;
		if (dfs(1, 0, mid), getmax() >= k + 1) r = mid - 1;
		else l = mid + 1;
	}
	dfs(1, 0, l);
	std::cout << Max(f[1][0], Max(f[1][1], f[1][2])) - l * (k + 1) << std::endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/83050085