首先要证明打表看出决策单调性.
然后是做法:
假设我们正在做 i = 6 i = 6 i=6位置, 现在 i i i后面的决策是这样的:
111113333344444555555
可以发现, 决策相同的点是一段连续的区间.
用队列维护 i i i位置之后的区间. 队列中的元素是个三元组 ( l , r , p ) (l, r, p) (l,r,p), 表示 [ l , r ] [l, r] [l,r]这段区间内的决策是 p p p.
做完 i i i后, 自然要考虑 i i i能改变哪些位置的最有决策, 比如可能变成这样:
111113333344444555555
111113333344444555666
或者这样
111113333344444555555
111113336666666666666
当然也可能这样
111113333344444555555
666666666666666666666
注意到, 可以被改变的区间一定是一段后缀.
所以可以二分一下.
具体来说, 我们先需要处理队尾的元素. 如果整个区间都是从 i i i转移更优, 那么直接出队即可. 判断是否整个区间都是最优的也很简单, 只需判断这个区间的左端点 l l l即可(因为是一段后缀).
如果某个区间的 l l l不是从 i i i转移更优, 而是他原来的转移点更优, 则证明这个区间内存在分界线, 最后的决策应该长这样:
ppppppppiiii
所以我们再二分这个区间, 找到第一个 i i i, 设位置为 l ′ l' l′, 修改队尾区间的 r = l ′ − 1 r = l' - 1 r=l′−1, 然后push一个 ( l ′ , n , i ) (l', n, i) (l′,n,i)进去, 就完成了.
当然如果没有任何点从 i i i转移更优就不用push了.
在取决策点的时候, 也需要二分 i i i在队列中的哪个区间里.
就, 写完了…
{{% code %}}
int tail = 0;
que[tail++] = Node{1, n, 0};
for (int i = 1; i <= n; i++) {
int cur = -1;
int L = 0, R = tail-1;
while (L <= R) {
int mid = (L+R)>>1;
if (que[mid].l <= i) {
cur = mid;
L = mid + 1;
}
else
R = mid - 1;
}
int p = que[cur].p;
dp[i] = dp[p] + w(p, i);
cur = -1;
while (tail && dp[i] + w(i, que[tail-1].l) <= dp[que[tail-1].p] + w(que[tail-1].p, que[tail-1].l))
cur = que[--tail].l;
L = que[tail-1].l, R = que[tail-1].r;
while (L <= R) {
int mid = (L+R) >> 1;
if (dp[i] + w(i, mid) <= dp[que[tail-1].p] + w(que[tail-1].p, mid)) {
que[tail-1].r = mid - 1;
R = mid - 1;
cur = mid;
}
else
L = mid + 1;
}
if (cur != -1)
que[tail++] = Node{cur, n, i};
}
{{% /code %}}