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