比利♂海灵顿
2022-01-13 16:45:57
凸优化 (Convex Optimisation), 和凸包优化(Convex Hull Optimisation)不同, 凸包优化强调的是构造凸包, 然后求出对应斜率切线的截距, 以此来转移, 所以凸包优化又叫斜率优化.
当然这里的凸优化和数学中的凸最小化也不同, 因为数学中求的是凸函数的最小值.
动态规划的凸优化则是用二分, 二分一个惩罚值, 将
因为是由王钦石在他的 2012 年国家集训队论文中提出的, 因此这种方法又叫 WQS 二分.
这貌似是我学的最后一种 DP 优化方式, 补全了 DP 优化的最后一张拼图. 由于凸优化是把状态降维, 不同于斜率优化和四边形不等式是优化转移, 所以我又把它称为 "降维打击".
接下来结合具体题目看一下它的应用
八省 (Eight Province, EP) 联考
一棵边带权的树, 权值是或正或负的整数, 简单路径的权值是组成路径的边的权值和, 选
因为节点是没有权值的, 如果已经选好了最优的断边方案, 一定可以在加边后找到一条路径, 连接所有
所以本题转化为将原树断成
最后转化为树上选
设计树形 DP, f_{i, j, 0/1/2} 表示
状态 -O2
可以拿到 -O2
也是
unsigned m, n;
unsigned A, B, D, t;
int C;
unsigned Cnt(0), Ans(0), Tmp(0);
struct Node {
Node* Fa;
vector <pair<Node*, int>> E;
long long f[105][3];
}N[300005];
inline void DFS(Node* x) {
memset(x->f, 0xaf, ((m + 1) * 3) << 3);
x->f[0][0] = x->f[1][1] = 0;
for (auto i : x->E) if (i.first != x->Fa) {
Node* Cur(i.first);
Cur->Fa = x, DFS(Cur);
for (unsigned j(m); j; --j) {
for (unsigned k(1); k <= j; ++k) {
long long Mx(max(Cur->f[k][0], max(Cur->f[k][1], Cur->f[k][2])));
if (Mx < 0) continue;
if (Cur->f[k][1] >= 0) {
x->f[j][2] = max(x->f[j][2], x->f[j - k + 1][1] + Cur->f[k][1] + i.second);
x->f[j][1] = max(x->f[j][1], x->f[j - k][0] + Cur->f[k][1] + i.second);
}
x->f[j][2] = max(x->f[j][2], x->f[j - k][2] + Mx);
x->f[j][1] = max(x->f[j][1], x->f[j - k][1] + Mx);
x->f[j][0] = max(x->f[j][0], x->f[j - k][0] + Mx);
}
}
}
}
signed main() {
n = RD(), m = RD() + 1;
for (unsigned i(1); i < n; ++i) {
A = RD(), B = RD(), C = RDsg();
N[A].E.push_back(make_pair(N + B, C));
N[B].E.push_back(make_pair(N + A, C));
}
DFS(N + 1);
printf("%lld\n", max(N[1].f[m][0], max(N[1].f[m][1], N[1].f[m][2])));
return Wild_Donkey;
}
如果我们输出大样例中, 不同的
发现答案上凸 (Convex Upward), 或者说, 下凹 (Concave Downward)
去掉问题中对路径数量的限制, 将状态降维成
降维打击强就强在, 当转移需要枚举一维状态的时候, 降一维状态相当于把复杂度降了两维, 所以这样一次 DP 的时间就变成了
如果我们记录每个状态具体选择的个数
当我们每增加一个新的路径, 就把答案减
因为答案上凸, 所以切点的横坐标一定随
不过因为答案序列也可能存在连续几个点共线的情况, 这时
long long Ans[300005];
unsigned m, n;
unsigned A, B, D, t;
long long L(-300000000000), R(300000000000), C;
unsigned Cnt(0), Tmp(0);
struct Node {
Node* Fa;
vector <pair<Node*, int>> E;
long long f[3];
unsigned g[3];
}N[300005];
inline void DFS(Node* x) {
x->f[0] = x->g[0] = 0, x->g[1] = 1, x->f[1] = -C, x->g[2] = x->f[2] = -100000000000000000;
for (auto i : x->E) if (i.first != x->Fa) {
Node* Cur(i.first);
Cur->Fa = x, DFS(Cur);
long long Des(x->f[1] + Cur->f[1] + i.second + C);
if (Cur->f[0] > 0) x->f[2] += Cur->f[0], x->g[2] += Cur->g[0];
if (x->f[2] < Des) x->f[2] = Des, x->g[2] = x->g[1] + Cur->g[1] - 1;
if (Cur->f[0] > 0) x->f[1] += Cur->f[0], x->g[1] += Cur->g[0];
Des = x->f[0] + Cur->f[1] + i.second;
if (x->f[1] < Des) x->f[1] = Des, x->g[1] = x->g[0] + Cur->g[1];
if (Cur->f[0] > 0) x->f[0] += Cur->f[0], x->g[0] += Cur->g[0];
}
if (x->f[1] > x->f[0]) x->f[0] = x->f[1], x->g[0] = x->g[1];
if (x->f[2] > x->f[0]) x->f[0] = x->f[2], x->g[0] = x->g[2];
}
signed main() {
n = RD(), m = RD() + 1;
for (unsigned i(1); i < n; ++i) {
A = RD(), B = RD(), C = RDsg();
N[A].E.push_back(make_pair(N + B, C));
N[B].E.push_back(make_pair(N + A, C));
}
B = 0, D = n;
while (L <= R) {
C = ((L + R) >> 1);
DFS(N + 1), A = N[1].g[0];
Ans[A] = N[1].f[0] + C * A;
if (A == m) { printf("%lld\n", Ans[m]);return 0; }
if (A > m) L = C + 1, D = A;
else R = C - 1, B = A;
}
printf("%lld\n", Ans[B] + (Ans[D] - Ans[B]) / (D - B) * (m - B));
return Wild_Donkey;
}
值得注意的几点:
二分下界是负数.
如果
原版
加强版增加了数据范围,
仍然是把数量限制去掉来降维, 状态
根据原版可知, 这个 DP 具有决策单调性, 所以我们可以记录每个状态可以作为哪个区间的最优决策来
unsigned long long f[500005], Sum[500005], L, R, C;
unsigned long long LAns, RAns, Ans;
unsigned Stack[500005][3], STop(0);
unsigned a[500005], g[500005], m, n;
unsigned A, B, D, t, LPos, RPos, Pos;
unsigned Cnt(0), Tmp(0);
inline unsigned long long Trans(unsigned x, unsigned y) { return f[y] + Sum[y] + Sum[x] + C - Sum[(x + y) >> 1] - Sum[(x + y + 1) >> 1]; }
inline long long Calc() {
STop = 0, Stack[++STop][0] = 1, Stack[STop][1] = n, Stack[STop][2] = 0;
for (unsigned i(1), j(1); i <= n; ++i) {
while (Stack[j][1] < i) ++j;
f[i] = Trans(i, Stack[j][2]);
g[i] = g[Stack[j][2]] + 1;
while ((STop > j) && (Trans(Stack[STop][0], Stack[STop][2]) >= Trans(Stack[STop][0], i))) --STop;
unsigned BL(Stack[STop][0]), BR(Stack[STop][1] + 1), BMid, Bef(Stack[STop][2]);
while (BL ^ BR) {
BMid = ((BL + BR) >> 1);
if (Trans(BMid, Bef) < Trans(BMid, i)) BL = BMid + 1;
else BR = BMid;
}
Stack[STop][1] = BL - 1;
if (BL <= n) Stack[++STop][0] = BL, Stack[STop][1] = n, Stack[STop][2] = i;
}
return f[n] - g[n] * C;
}
signed main() {
n = RD(), m = RD();
for (unsigned i(1); i <= n; ++i) a[i] = RD();
sort(a + 1, a + n + 1);
for (unsigned i(1); i <= n; ++i) Sum[i] = Sum[i - 1] + a[i];
L = 0, C = R = Sum[n], LPos = 1, RPos = n, LAns = Calc(), RAns = 0;
while (L <= R) {
C = ((L + R) >> 1);
Ans = Calc(), Pos = g[n];
if (Pos == m) { printf("%llu\n", Ans);return 0; }
if (Pos < m) R = C - 1, LPos = Pos, LAns = Ans;
else L = C + 1, RPos = Pos, RAns = Ans;
}
printf("%llu\n", LAns - (LAns - RAns) / (RPos - LPos) * (m - LPos));
return Wild_Donkey;
}
其实凸优化的过程有点求导的几何意义的那种感觉了, 最后多点共线又多少带点微分中值定理的意义, 所以可能也可以称其为微分优化. (误