四边形不等式优化dp 学习笔记
对于 \(f_i\) ,有:
其中 \(w(j,i)\) 为区间 \([j,i]\) 的权值,\(w(i,j)\) 的值不会改变。记 \(K_i\) 为
\(i\) 的最优决策点,也就有 \(f_i=f_{K_i-1}+w(K_i,i)\)。
如果 \(w(i,j)\) 满足四边形不等式,则有 \(\forall i\in[1,n-1]\),有 \(K_i\le K_{i+1}\),也就是有 \(K_1\le K_2\le ...\le K_n\),这样就具有 决策单调性。
四边形不等式
对于 \(a\le b\le c\le d\),有
也就是 相交 \(\le\) 包含。
注:若表达式为取 \(\max\) 的话会反过来,也就是相交 \(\ge\) 包含。其实总结一下,就是 相交优于包含。
更常见的形式是,对于 \(i\le i+1\le j-1\le j\),有
或者写成差分形式,为
这样就方便证明 \(w(i,j)\) 满足四边形不等式。
为什么满足四边形不等式就有决策单调性?
证明:
考虑反证。假设存在 \(i<j\) 满足 \(K_i>K_j\)。
因为有取 \(\min\),根据最优性,一定有:
两式相加,则有:
左右两边同时减去 \(dp_{K_i-1}+dp_{K_j-1}\) ,则有:
而又因为有 \(K_j<K_i\le i<j\) 且 \(w(i,j)\) 满足四边形不等式。所以代入 \(K_j,K_i,i,j\) 则有:
那么上下两个不等式都成立当且仅当中间取等号,那么就有 \(K_i=K_j\),不符合 \(K_i>K_j\)。所以上述两个不等式相互矛盾,假设不成立。
得证。
对于决策单调性的题目,通常有两种实现方式。
实现方式1:分治
需要满足转移跟枚举的顺序无关。
就如有转移式
此处 \(g\) 数组的值已知。那么就可以用分治实现。
对于任意区间 \([l,r]\),设 \(mid=\lfloor\frac{l+r}{2}\rfloor\)。
首先对于 \([1,n]\) ,我们可以先求出 \(K_{mid}\) 的值。我们知道 \(1\le K_{mid}\le n\),所以可以枚举 \([1,n]\),再取最小的为 \(K_{mid}\)。
现在要分治下去了。
-
那么对于 \(\forall i\in[1,mid-1]\) ,一定有 \(1\le K_i\le K_{mid}\),所以在 \([1,K_{mid}]\) 里找新的 \(K\) 值。
-
而对于 \(\forall i\in[mid+1,n]\),一定有 \(K_{mid}\le K_i\le n\),在 \([K_{mid},n]\) 里找新的 \(K\) 值。以此递归。
所以抽象地说。对于区间 \([l,r]\),在 $[K_{l-1},K_{r+1}] $ 里找到最小的转移值,最小转移值对应位置则为 \(K_{mid}\)。然后再分治下去。
初始的时候可以让 \(K_0=1,K_{n+1}=n\)。
单次一维dp时间复杂度 \(O(n\log n)\)
参考代码:
k[0]=1,k[n+1]=n;
void div(int l,int r,int j)
{
if(l>r) return;
int L=k[l-1],R=k[r+1],mid=(l+r)>>1;
for(int i=L;i<=R;i++)//更新 k[mid] 的值
{
if(w(i,mid)+g[i]<dp[mid])
{
dp[mid]=w(i,mid)+g[i];
k[mid]=i;
}
}
div(l,mid-1,j),div(mid+1,r,j);//分治下去
}
同样,对于转移式如下的二维dp也可以这样搞:
因为 \(dp_{k-1,j}\) 是已知的了。
实现方式2:二分+双端队列
那么什么情况不能用分治?就如开篇的第一个转移式:
如果先去更新 \(f_{mid}\) ,连 \(f_{0\sim mid-1}\) 的值都不知道怎么判断哪个是最优决策点?
所以现在要考虑另一种方法了。
因为必须顺序枚举,考虑动态更新维护 \(K\) 值。
初始时, \(K=\{1,1,...,1\}\)。现在我们已经求出 \(f_1\) 的值了,对应的 \(j\) 为 \(2\)。那我们就去二分找最小的位置满足这个点用 \(2\) 转移比用 \(1\) 转移更优。根据决策单调性,那么它后面的所有位置用 \(2\) 转移都更优。所以 \(K=\{1,1,1,...,1,2,2,2,...,2\}\)。
然后就不断去计算 \(f_{j-1}\),然后二分更新后面的 \(K\) 值。
现在涉及到一个区间覆盖问题。可以珂朵莉树,可以线段树,但是太麻烦了。能不能更简单一点?
我们发现,任何时候一种 \(K\) 值都是在一段里的。所以考虑用队列依次记录下每一种 \(K\) 值。同时,开一个数组 \(pos_i\) 为 \(K_j=i\) 的最小的 \(j\),也就是 \(i\) 这种 \(K\) 值最前面的位置。
现在考虑更新了。因为可能存在更新完后,某几种 \(K\) 值就不存在了。所以考虑从队尾开始,依次判断队尾 \(K\) 值的第一个位置用新 \(K\) 值转移是否更优。如果更优那么直接弹出队尾,因为这种 \(K\) 值会被完全覆盖掉从而消失。
直到找到第一个不满足要求的为止。设该不满足要求的 \(K\) 值为 \(x\)。则 \(x\) 不会被完全覆盖,且覆盖的始端是在 \(pos_x\) 以后。所以再在 \((pos_x,n]\) 中二分找覆盖的始点,将新 \(K\) 值的 \(pos\) 更新为这个点的下标,然后将这个 \(K\) 值放入队尾。
然后是单点查 \(K_i\)。因为 \(i\) 是单增的。所以当队首的后一个 \(K\) 值的 \(pos\) 值 \(\le i\) 时则弹出队首。然后最终的队首就是 \(K_i\)。
时间复杂度 \(O(n\log n)\)。该方法适用范围比较广泛。
参考代码:
int sum(int a,int b)
{
return dp[a-1]+w(a,b);//从a转移到b的转移值
}
void dp()
{
hd=tl=1;
q[tl]=1;
for(int i=1;i<=n;i++)
{
while(hd<tl&&pos[q[hd+1]]<=r) hd++;
int k=q[hd];
dp[i]=sum(k,i);//查询
while(hd<tl&&sum(r+1,pos[q[tl]])<sum(q[tl],pos[q[tl]])) tl--;//如果被完全覆盖则删去
int l=max(i,pos[q[tl]])+1,r=n,ans=n+1;
while(l<=r)//二分答案找覆盖的范围
{
int mid=(l+r)>>1;
if(sum(i+1,mid)<sum(q[tl],mid)) r=mid-1,ans=mid;
else l=mid+1;
}//更新
pos[i+1]=ans;
q[++tl]=i+1;
}
}
因为码量会稍微大一点且常数会大一点,所以能写分治还是习惯写分治。
练习题:
-
P1880 [NOI1995] 石子合并 \(O(n^2)\) 四边形不等式优化区间dp
-
P4767 [IOI 2000] 邮局 加强版 可以写分治实现
P6246 [IOI 2000] 邮局 加强版 加强版 需要加wqs二分,不能写分治实现
-
CF2073K Book Sorting 贪心+决策单调性,题解

浙公网安备 33010602011771号