P4383 [八省联考2018]林克卡特树lct 树形DP+凸优化/带权二分
$ \color{#0066ff}{ 题目描述 }$
小L 最近沉迷于塞尔达传说:荒野之息(The Legend of Zelda: Breath of The Wild)无法自拔,他尤其喜欢游戏中的迷你挑战。
游戏中有一个叫做“LCT” 的挑战,它的规则是这样子的:现在有一个N 个点的 树(Tree),每条边有一个整数边权vi ,若vi >= 0,表示走这条边会获得vi 的收益;若vi < 0 ,则表示走这条边需要支付- vi 的过路费。小L 需要控制主角Link 切掉(Cut)树上的 恰好K 条边,然后再连接 K 条边权为 0 的边,得到一棵新的树。接着,他会选择树上的两个点p; q ,并沿着树上连接这两点的简单路径从p 走到q ,并为经过的每条边支付过路费/ 获取相应收益。
海拉鲁大陆之神TemporaryDO 想考验一下Link。他告诉Link,如果Link 能切掉 合适的边、选择合适的路径从而使 总收益 - 总过路费最大化的话,就把传说中的大师之剑送给他。
小 L 想得到大师之剑,于是他找到了你来帮忙,请你告诉他,Link 能得到的 总收益 - 总过路费最大是多少。
\(\color{#0066ff}{输入格式}\)
从文件lct.in 中读入数据。
输入第一行包含两个正整数N; K,保证0 <= K < N <= 3*\(10^5\)。
接下来N - 1 行,每行包含三个整数xi; yi; vi,表示第i 条边连接图中的xi; yi 两点, 它的边权为vi。
\(\color{#0066ff}{输出格式}\)
输出到文件lct.out 中。
输出一行一个整数,表示答案。
\(\color{#0066ff}{输入样例}\)
5 1
1 2 3
2 3 5
2 4 -3
4 5 6
\(\color{#0066ff}{输出样例}\)
14
\(\color{#0066ff}{数据范围与提示}\)
【样例1 解释】
一种可能的最优方案为:切掉(2; 4; ?3) 这条边,连接(3; 4; 0) 这条边,选择(p; q) = (1; 5)。
• 对于10% 的数据,k = 0 ;
• 对于另外10% 的数据,k = 1 ;
• 对于另外15% 的数据,k = 2 ;
• 对于另外25% 的数据,k <= 100 ;
• 对于其他数据,没有特殊约定。
对于全部的测试数据,保证有1 <= N <= 3 * \(10^5\); 1 <= xi; yi <= N; |vi| <= \(10^6\) 。
【提示】
题目并不难。
\(\color{#0066ff}{题解}\)
带权二分,这篇题解写的比较详细,转载自 EternalAlexander
#include<bits/stdc++.h>
#define LL long long
LL in() {
char ch; LL x = 0, f = 1;
while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
return x * f;
}
const int maxn = 3e5 + 100;
struct node {
LL mx, k;
node(LL mx = 0, LL k = 0): mx(mx), k(k) {}
friend node operator + (const node &a, const node &b) { return node(a.mx + b.mx, a.k + b.k); }
friend bool operator < (const node &a, const node &b) { return (a.mx < b.mx || (a.mx == b.mx && a.k < b.k)); }
}f[maxn][3];
struct EDGE {
int to; LL dis;
EDGE *nxt;
EDGE(int to = 0, LL dis = 0, EDGE *nxt = NULL): to(to), dis(dis), nxt(nxt) {}
};
EDGE *head[maxn];
LL n, k, mid, ans, B;
void dfs(int x, int fa) {
f[x][0] = f[x][1] = node(0, 0);
f[x][2] = node(-mid, 1);
for(EDGE *i = head[x]; i; i = i->nxt) {
if(i->to == fa) continue;
dfs(i->to, x);
f[x][2] = std::max(f[x][2] + f[i->to][0], f[x][1] + f[i->to][1] + node(i->dis - mid, 1));
f[x][1] = std::max(f[x][1] + f[i->to][0], f[x][0] + f[i->to][1] + node(i->dis, 0));
f[x][0] = f[x][0] + f[i->to][0];
}
f[x][0] = std::max(f[x][0], std::max(f[x][1] + node(-mid, 1), f[x][2]));
}
void add(int from, int to, int dis) { head[from] = new EDGE(to, dis, head[from]); }
LL ok() {
dfs(1, 0);
B = f[1][0].mx;
return f[1][0].k;
}
int main() {
n = in(), k = in() + 1;
LL l, r = 0;
LL x, y, z;
for(int i = 1; i < n; i++) {
x = in(), y = in(), z = in();
add(x, y, z), add(y, x, z);
r += z > 0? z : -z;
}
l = -r;
while(l <= r) {
mid = (l + r) >> 1;
if(ok() >= k) l = mid + 1, ans = mid;
else r = mid - 1;
}
mid = ans;
ok();
printf("%lld\n", B + ans * k);
return 0;
}