考虑有一类问题是选择 (k) 个啥然后求最优代价
如果打表发现这个 (k) 和代价 (f(k)) 构成的函数是上凸的,那么可以考虑 (WQS) 二分
具体的思想就是说因为要求的是 (f_{max}(x)) 那么可以考虑用一个 (y=kx+b) 去切这个函数
如果我们在 (k=0) 的时候得到的 (b) 就是 (f_{max}(x))
如果给定了 (x) 的值也就做出来了
那么考虑移项得到 (b=f(x)-kx)
那么我们对于所有的物品增加一个附加权值 (-mid)
这样子在 (check) 的时候就可以正常 (dp) 了
我们转移的时候记录下来决策点 (x) 的量
如果以 (HEOI2018) 林克卡特树为例那么那么就是维护出来链的数量和 (K) 进行比较
这样子可以考虑改变附加权值的大小
最后二分斜率得到答案带入找到切点得出答案
如果二分的时候出现了有多个决策点的情况,那么记录下来 (x_{max}) 最后带入求值
可能会写林克卡特树了,别的留坑
题解:
首先感性理解一下这题目关于 (k) 一定是一个上凸函数
然后考虑 (dp) 求若干个不相交的链的和
设 (f_{i,0/1/2}) 表示当前点和当前链的关系
(0) 是不在链上,(1) 表示在链的一端,(2) 表示在链的中间
转移考虑合并链的情况即可,每新加一个链就剪掉当前二分的 (mid)
最后转移出来一个 (f[1]_ {max}.cnt) 和 (k) 比较,改变斜率
这里注意这样的状态定义需要把输入的 (k) 加上 (1)
代码如下:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define reg register
namespace yspm {
inline int read() {
int res = 0, f = 1;
char k;
while (!isdigit(k = getchar()))
if (k == '-')
f = -1;
while (isdigit(k))
res = res * 10 + k - '0', k = getchar();
return res * f;
}
const int N = 3e5 + 10;
struct nd {
int to, nxt, dis;
} e[N << 1];
int head[N], tot, n, m, k;
inline void add(int u, int v, int w) {
e[++tot].to = v;
e[tot].nxt = head[u];
e[tot].dis = w;
return head[u] = tot, void();
}
struct node {
int dp, cnt;
node() {};
node(int x, int y) {
dp = x;
cnt = y;
return;
}
bool operator<(const node &a)const {
if (dp ^ a.dp)
return dp < a.dp;
return cnt < a.cnt;
}
node operator+(const node &a)const {
return node(dp + a.dp, cnt + a.cnt);
}
inline void print(){
cout<<dp<<" "<<cnt<<endl;
}
} f[N][3];
inline node max(node a, node b) {
return a < b ? b : a;
}
inline int max(int x, int y) {
return x < y ? y : x;
}
inline int calc(int x) {
return max(f[x][1].dp, max(f[x][0].dp, f[x][2].dp));
}
inline void dfs(int x, int fa, int now) {
f[x][0] = node(0, 0);
f[x][1] = node(-1e13, 0);
f[x][2] = node(-now, 1);
for (reg int i = head[x]; i; i = e[i].nxt) {
int t = e[i].to;
if (t == fa)
continue;
dfs(t, x, now);
node mxt = max(f[t][0], max(f[t][1], f[t][2]));
f[x][2] = max(f[x][2] + mxt, f[x][1] + max(node(f[t][0].dp + e[i].dis, f[t][0].cnt),
node(f[t][1].dp + e[i].dis + now, f[t][1].cnt - 1)));
f[x][1] = max(f[x][1] + mxt, f[x][0] + max(node(f[t][0].dp + e[i].dis - now, f[t][0].cnt + 1),
node(f[t][1].dp + e[i].dis, f[t][1].cnt)));
f[x][0] = f[x][0] + mxt;
}
return ;
}
signed main() {
n = read();
k = read()+1;
for (reg int i = 1, u, v, w; i < n; ++i)
u = read(), v = read(), w = read(), add(u, v, w), add(v, u, w);
int l = -1e13 - 10, ans = 0, r = 1e13 + 10;
while (l <= r) {
int mid = (l + r) >> 1;
dfs(1, 0, mid);
node mx = max(f[1][0], max(f[1][1], f[1][2]));
if (mx.cnt < k)
r = mid - 1;
else
l = mid + 1, ans = mid;
}
dfs(1, 0, ans);
printf("%lld
", calc(1) + k * ans);
return 0;
}
}
signed main() {
return yspm::main();
}
(loj) 格式化之后我自己都看不懂了
然后是一个考试题的题解
求一个长度为 (n) 的不相交的 (k) 个子段的最大和
这个东西感觉就很凸函数,毕竟把正数都选完了就会出现权值减小
那么二分最优转移点,对于每个选择的段加上一个权值
也就是说,在 (WQS) 二分中,选择什么,就对什么加权
然后 (dp) 记录最大值的位置做一下就行了
这里判断最大切点的时候使用了
(f_x-sum_xge f_{mx}-sum_{mx})
感觉上是因为找最大值,但是理解仍然不是很深刻
留坑