定义
如果有一个 d p dp dp问题,有一个维度是选择的个数,并且限制个数恰等于 m m m,由于这个维度的加入,暴力 d p dp dp的复杂度难以接受,即使转移是 O ( 1 ) O(1) O(1)的,往往状态数 O ( n m ) O(nm) O(nm)就已经爆了。
如果我们不考虑 m m m这个维度,剩下的往往就是一个线性 d p dp dp,可以在 O ( n ) O(n) O(n)或 O ( n log n ) O(n\log n) O(nlogn)复杂度内解决。那么核心问题就是,怎么加速 m m m这个维度的处理?
如果 d p dp dp结果,也就是最小总代价,关于选择的个数 x x x的函数 g ( x ) g(x) g(x),是个凸函数,那么我们可以规定,每一次选择有个额外代价Δ,然后二分,在最优情况下,使得选择个数不小于 m m m的最大Δ, c h e c k check check里不用考虑恰好选 m m m个的约束了,直接线性 d p dp dp计算选任意个的最小代价,以及取到最小代价时,选了几个。
这个二分Δ的过程就是 W Q S WQS WQS(王钦石)二分(国外也叫Alien Trick,因为最早在一道叫Alien的题目被引入),最后我们找到了最大Δ,在这个Δ下进行 c h e c k check check里的,不考虑个数约束的 d p dp dp,计算出的就是恰好选 m m m个的最小代价。这样我们就在只增加了一个二分Δ的 log \log log复杂度的条件下,解决了恰好选 m m m个这个约束。(实际上算出来的是考虑Δ的,我们选了 m m m个,减掉 m ∗ Δ m*Δ m∗Δ才是最终答案)
原理
为什么是对的?有两种解释
形象的解释
比较形象的解释是,选 m m m个东西看成消费者在市场上买东西,使得买的东西总价格最小,这是一个优化问题,可以 d p dp dp。然后政府给这个商品加税,每件商品固定加税Δ。消费者的目标仍然是使得总价格最小,那么考虑到税Δ,显然会影响到消费者买的东西个数,Δ越大,买的东西越少。
如果政府想限制消费者恰好只买 m m m个东西,可以通过加税来控制消费者的行为,或者说,让消费者的最优决策点移动(在 g ( x ) g(x) g(x)这个函数上移动, x x x是购买物品个数, g ( x ) g(x) g(x)是最小代价,这个函数上是不同Δ下的最优决策点)。
政府也就是 w q s wqs wqs二分,只用考虑如何让消费者恰好买 m m m个东西,消费者,也就是 c h e c k check check里的 d p dp dp,只用在给定Δ下,不限制购买格式,考虑如何最小化代价。最后通过政府,消费者两级优化,能得到恰好买 m m m个东西的最小代价。
严谨的分析
规定每个东西有额外代价Δ,等价于把优化目标改成最小化 g ( x ) + x ∗ Δ g(x)+x*Δ g(x)+x∗Δ,当Δ,变化,显然最小值点 x x x也会变化,求导可得最小值点就是 g ′ ( x ) = − Δ g^{'}(x)=-Δ g′(x)=−Δ的点。(这里也可以看成一个直线 y = − Δ ∗ x y=-Δ*x y=−Δ∗x,求和 g ( x ) g(x) g(x)的切点)
那么如果 g ( x ) g(x) g(x)是凸函数,随着Δ单调变化,最小值点也是单调变化的。所以我们可以通过二分Δ,来使得最小值点,来确定使得最小值点,也就是选择个数,恰为 m m m的Δ。
接下来对于这个Δ,跑 d p dp dp,最优解必然是恰好选择 m m m个的,这就满足了我们的要求。
这东西应该属于凸优化里的拉格朗日法,只是被引入到算法竞赛里了。
例题
太干燥了,看个具体的例子
这是我在四边形不等式优化dp这篇文章里引用过的例题,当时利用决策单调性,可以实现最优解为 O ( n m ) O(nm) O(nm),或者说 O ( P V ) O(PV) O(PV)。这个复杂度对于加强后的本题 n , m = 5 e 5 n,m=5e5 n,m=5e5是过不去的。
对于 n n n个房子这个维度,可以用决策单调性优化到 O ( n log n ) O(n\log n) O(nlogn)甚至 O ( n ) O(n) O(n),问题在于还有恰好建 m m m个邮局这个维度。
这个约束看起来就像是 W Q S WQS WQS二分擅长的,现在需要证明 g ( x ) g(x) g(x)是凸函数,就可以套 W Q S WQS WQS了。凸性先考虑定性证明,随着邮局个数增加,显然总的距离和是减小的,并且随着邮局个数越来越多,每新建一个邮局,距离和的减小量会越来越小,也就是边际效益递减,这样画出函数图像类似于一个开口向上的二次函数的左半边,单减,但是减小的1越来越慢。显然是凸函数。
那么我们可以外层套 W Q S WQS WQS二分,解决恰好 m m m个邮局这个约束,复杂度 O ( log ∑ x i ) O(\log \sum x_i) O(log∑xi),这复杂度是因为二分的值Δ可以看成,去切 g ( x ) g(x) g(x)的切线斜率,所以Δ值域应该就是 g ( x ) g(x) g(x)斜率的值域,最小 0 0 0,由于 g ( x ) g(x) g(x)实际上是多个散点组成的,斜率等价于差分,最大斜率就是最大的差分 g ( 1 ) − g ( 0 ) g(1)-g(0) g(1)−g(0),从选 0 0 0个到选 1 1 1个,这不会超过把唯一的邮局建在 0 0 0的代价( ∑ x i \sum x_i ∑xi),故 ∑ x i \sum x_i ∑xi是个很宽的上界。
内层 c h e c k check check里利用决策单调性进行 d p dp dp,求最小代价。内层就是一个只有一层的线性 d p dp dp,无法分治或者记录决策点,只能二分队列,复杂度 O ( n log n ) O(n\log n) O(nlogn)、
总复杂度 O ( n log n log ∑ x i ) O(n\log n\log \sum x_i) O(nlognlog∑xi),可以通过 n , m = 5 e 5 n,m=5e5 n,m=5e5的数据
c
int a[N], s[N], n, m;
int f[N], g[N];
inline ll w(int i, int j) {
if (i > j) return 0;
int mid = (i + j) >> 1;
return (a[mid] * (mid - i) - (s[mid - 1] - s[i - 1])) +
((s[j] - s[mid]) - a[mid] * (j - mid));
}
inline ll calc(int delta, int k, int j) {
return f[k] + w(k + 1, j) + delta;
}
int find_pos(int delta, int x, int y, int l, int r) {
while (l <= r) {
int mid = (l + r) / 2;
if (calc(delta, x, mid) <= calc(delta, y, mid)) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return l;
}
struct node {
int k, l, r;
} q[N];
bool check(int delta) {
int head = 1, tail = 1;
q[1] = {0, 1, n};
f[0] = 0;
g[0] = 0;
rep(j, 1, n) {
while (head < tail && q[head].r < j) {
head++;
}
f[j] = calc(delta, q[head].k, j);
g[j] = g[q[head].k] + 1;
int pos = -1;
while (head <= tail) {
if (calc(delta, j, q[tail].l) <= calc(delta, q[tail].k, q[tail].l)) {
pos = q[tail].l;
tail--;
} else {
if (calc(delta, j, q[tail].r) <= calc(delta, q[tail].k, q[tail].r)) {
pos = find_pos(delta, j, q[tail].k, q[tail].l, q[tail].r);
q[tail].r = pos - 1;
}
break;
}
}
if (pos != -1) {
q[++tail] = {j, pos, n};
}
}
return g[n] >= m;
}
void solve() {
cin >> n >> m;
rep(i, 1, n) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
int l = 0, r = s[n], ans = 0;
while (l <= r) {
int mid = (l + r) / 2;
if (check(mid)) {
l = mid + 1;
ans = mid;
} else {
r = mid - 1;
}
}
check(ans);
cout << f[n] - ans*m;
}