决策单调性优化DP 学习笔记
概述
决策单调性是 DP 的一种性质。
用 \(a\)D / \(b\)D 描述一个 DP 方程,其中 \(a\) 表示状态数为 \(O(n^a)\),\(b\) 表示一个状态的转移需要用到 \(O(n^b)\) 个之前的状态。
决策单调性一般体现在 1D / 1D 或 2D / 1D 的 DP 中,也就是。对于每一层状态,定义每个位置转移的前驱为最优决策点,记作 \(p_i\)。比如方程 \(f_i=\min_{j=1}^{i-1}f_j+w(j,i)\) 中,\(p_i\) 为使得 \(f_j+w(j,i)\) 最小的 \(j\)(若有多个 \(j\) 取到最小值,总是取最小或最大的 \(j\),下面取最小的)。决策单调性指的是同一层中 \(\forall i<j,p_i\leq p_j\)。
四边形不等式
四边形不等式是证明决策单调性的常用方法。
先看一种 1D / 1D 方程 \(f_i=\min_{j=1}^{i-1}f_j+w(j,i)\)。取 \(\max\) 同理。这相当于将一个序列分段,最小化或最大化每段的权值之和。
结论:若 \(\forall a\leq b\leq c\leq d,w(a,c)+w(b,d)\leq w(a,d)+w(b,c)\),则 \(f\) 有决策单调性。这被称为四边形不等式。
证明:若存在 \(p_d<p_c<c<d\),则 \(f_c+w(p_c,c)<f_d+w(p_d,c),f_d+w(p_d,d)\leq f_c+w(p_c,d),w(p_d,c)+w(p_c,d)>w(p_d,d)+w(p_c,c)\),矛盾。
同理,对于 2D / 1D 方程 \(f_{k,i}=\min_{j=1}^{i-1}f_{k-1,j}+w(j,i)\) 也可以得到类似的结论。这个方程相当于将序列分成 \(k\) 段。
对于这种方程有更强的结论:若 \(w\) 满足四边形不等式,则 \(p_{k-1,i}\leq p_{k,i}\leq p_{k,i+1}\)。
证明:
第二个不等号为决策单调性。
感性理解一下,如果从右向左分段,也满足决策单调性。
假如 \(p_{k-1,i}>p_{k,i}\),从右往左用决策单调性,有 \(p_{k-2,p_{k-1,i}-1}>p_{k-1,p_{k,i}-1}\),也就是 \(i\) 在 \(k-1\) 层的上两个分段的位置大于 \(k\) 层的上两个分段的位置。这样可以推得 \(i\) 在 \(k-1\) 层的上 \(k-1\) 个分段的位置大于 \(k\) 层的上 \(k-1\) 个分段的位置。而前者为 \(0\),矛盾。
对于另一种 2D / 1D 方程 \(f_{i,j}=\min_{k=i}^{j-1}f_{i,k}+f_{k+1,j}+w(i,j)\),有类似的结论。若 \(w\) 满足四边形不等式,且 \(\forall a\leq b\leq c\leq d,w(b,c)\leq w(a,d)\)(区间包含单调性),则 \(p_{i,j-1}\leq p_{i,j}\leq p_{i+1,j}\)。
证明:
若 \(w\) 满足四边形不等式和区间包含单调性,则 \(f\) 满足四边形不等式。
考虑数学归纳法,按区间长度归纳。当 \(d=a\),显然成立。
当 \(c\leq p_{a,d}\):
\(p_{a,d}<b\) 同理。
当 \(b\leq p_{a,d}<c\):
不妨设 \(p_{b,c}\leq p_{a,d}\)。
当 \(i\) 固定时,方程可以看做 \(f_j=\min_{k=i}^{j-1} f_k+f_{k+1,j}+w(j)\),设新的代价函数为 \(w'(k,j)=f_{k+1,j}+w(j)\)。则 \(w'(a,d)+w'(b,c)=f_{a+1,d}+w(d)+f_{b+1,c}+w(c)\geq f_{a+1,c}+w(c)+f_{b+1,d}+w(d)=w'(a,c)+w'(b,d)\)。因此 \(w'\) 满足四边形不等式,根据上面的结论,\(f\) 在 \(i\) 相同时有决策单调性,即 \(p_{i,j-1}\leq p_{i,j}\)。同理 \(p_{i,j}\leq p_{i+1,j}\)。
证明四边形不等式,可以作差后根据函数本身、凸性等判断。\(w(a,d)-w(a,c)=f(d-a)-f(d-a-(d-c)),w(b,d)-w(b,c)-f(d-b)-f(d-b-(d-c))\),所以将 \(w\) 变成与 \(i,j\) 的距离有关的函数后根据凸性就能证明。
直接优化
对于这两种 2D / 1D 方程,可以记录一下决策点直接优化。有 \(p_{i,j}\leq p_{i-1,j}\leq p_{i+1,j+1}\),因此对 \(i-j\) 相同的状态,枚举的复杂度为 \(O(n)\),总复杂度 \(O(n(n+m))\)。
典题,石子合并,\(w(i,j)=\sum_{k=i}^j a_k\)。
区间包含单调性和四边形不等式显然满足。
那么根据上面的结论,记录一下决策点即可。
然而最大值不能用四边形不等式。分析性质,取到最大值应该让合并次数尽量大,且让石子数多的堆合并更多次。因此每次合并都是一个单独的堆合并进已经合并过的堆,即最优决策点是 \(l\) 或 \(r-1\)。
#include<bits/stdc++.h>
using namespace std;
int ans=0x3f3f3f3f,n,a[205],f[205][205],m[205][205],sum[205];
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],a[n+i]=a[i];
for(int i=1;i<=n*2;i++)sum[i]=sum[i-1]+a[i],m[i][i]=i;
for(int i=2;i<=n;i++){
for(int l=1,r=i;r<=n*2;l++,r++){
f[l][r]=0x3f3f3f3f;
for(int k=m[l][r-1];k<=m[l+1][r];k++)if(f[l][r]>f[l][k]+f[k+1][r]+sum[r]-sum[l-1])f[l][r]=f[l][k]+f[k+1][r]+sum[r]-sum[l-1],m[l][r]=k;
}
}
for(int i=1;i<=n;i++)ans=min(ans,f[i][i+n-1]);
cout<<ans<<'\n',memset(f,0,sizeof(f)),ans=-0x3f3f3f3f;
for(int i=1;i<=n*2;i++)sum[i]=sum[i-1]+a[i];
for(int i=2;i<=n;i++)for(int l=1,r=i;r<=n*2;l++,r++)f[l][r]=max(f[l][r-1],f[l+1][r])+sum[r]-sum[l-1];
for(int i=1;i<=n;i++)ans=max(ans,f[i][i+n-1]);
return cout<<ans<<'\n',0;
}
分段类问题。设 \(u\) 的前缀和为 \(s\),则 \(w(i,j)=\frac 1 2(s_{j,j}-s_{i-1,j}-s_{j,i-1}+s_{i-1,i-1})\),也就是 \((i,i),(j,j)\) 围成的矩形的一半,画个图可以知道 \(w(a,c)+w(b,d)\) 比 \(w(a,d)+w(b,c)\) 少了两块。然后套用 \(p_{k-1,i}\leq p_{k,i}\leq p_{k,i+1}\) 即可。
#include<bits/stdc++.h>
using namespace std;
char buf1[2097152],*ip1=buf1,*ip2=buf1;
inline int getc(){
return ip1==ip2&&(ip2=(ip1=buf1)+fread(buf1,1,2097152,stdin),ip1==ip2)?EOF:*ip1++;
}
template<typename T>void in(T &a)
{
T ans=0;
char c=getc();
for(;c<'0'||c>'9';c=getc());
for(;c>='0'&&c<='9';c=getc())ans=ans*10+c-'0';
a=ans;
}
template<typename T,typename... Args>void in(T &a,Args&...args)
{
in(a),in(args...);
}
int n,m,a[4005][4005],f[805][4005],p[805][4005];
int calc(int l,int r){
return (a[r][r]-a[l-1][r]-a[r][l-1]+a[l-1][l-1])/2;
}
int main(){
in(n,m);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)in(a[i][j]),a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
for(int i=1;i<=n;i++)f[1][i]=calc(1,i),p[1][i]=1;
for(int k=2;k<=m;k++){
p[k][n+1]=n-1;
for(int i=n;i>=1;i--){
f[k][i]=0x3f3f3f3f;
for(int j=p[k-1][i];j<=p[k][i+1];j++)if(f[k][i]>f[k-1][j]+calc(j+1,i))f[k][i]=f[k-1][j]+calc(j+1,i),p[k][i]=j;
}
}
return cout<<f[m][n]<<'\n',0;
}
决策单调性分治
考虑进行一个分治:设当前由上一层的 \([l_1,r_1]\) 转移到这一层的 \([l_2,r_2]\)。遍历求出 \([l_2,r_2]\) 的中点 \(mid\) 的决策点 \(p_{mid}\)。此时根据决策单调性可以分治到 \((l_1,p_{mid},l_2,mid-1),(p_{mid},r_1,mid+1,r_2)\)。分治 \(O(\log n)\) 层,每一层遍历的区间只有端点处相交,也就是 \(O(n)\),因此转移一层为 \(O(\log n)\)。这样在不知道上面的结论时也可以 \(O(mn\log n)\) 做。
一个技巧:有时 \(w\) 难以计算,比如区间颜色数,区间逆序对数等。联想到莫队,考虑直接维护两个指针暴力移动。对于每一层,左端点还是遍历每一个 \([l_1,r_1]\),而右端点只会从左往右来到每个 \(mid\),都是 \(O(n)\),因此复杂度不变。
\(w(i,j)\) 为区间颜色数。
感性证一下:\(w(a,d)-w(a,c)\) 相当于 \([c+1,d]\) 在 \([a,c]\) 中没出现的个数,\(w(b,d)-w(b,c)\) 相当于 \([c+1,d]\) 在 \([b,c]\) 中没出现的个数,显然后者更大,因此 \(w(b,d)-w(b,c)\geq w(a,d)-w(a,c),w(a,c)+w(b,d)\geq w(a,d)+w(b,c)\)。然后套用上方类莫队算法即可。
#include<bits/stdc++.h>
using namespace std;
int n,k,a[35005],f[55][35005],l=1,r,now,cnt[35005];
void add(int x){
now+=!cnt[x]++;
}
void del(int x){
now-=!--cnt[x];
}
int calc(int gl,int gr){
while(l>gl)add(a[--l]);
while(r<gr)add(a[++r]);
while(l<gl)del(a[l++]);
while(r>gr)del(a[r--]);
return now;
}
void solve(int l1,int r1,int l2,int r2,int p){
if(l2>r2)return;
int mid=(l2+r2)>>1,pos=mid;
for(int i=l1;i<=min(mid,r1);i++)if(f[p-1][i-1]+calc(i,mid)>f[p][mid])f[p][mid]=f[p-1][i-1]+calc(i,mid),pos=i;
solve(l1,pos,l2,mid-1,p),solve(pos,r1,mid+1,r2,p);
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=k;i++)solve(1,n,1,n,i);
return cout<<f[k][n]<<'\n',0;
}
\(w(a,d)-w(a,c)\) 可以拆成 \([c+1,d]\) 的逆序对数和 \([c+1,d]\) 对 \([a,c]\) 的贡献,\(w(b,d)-w(b,c)\) 同理,因此 \(w(a,d)-w(a,c)\geq w(b,d)-w(b,c)\),满足四边形不等式。分治后用树状数组维护即可。
#include<bits/stdc++.h>
using namespace std;
int n,k,a[25005],l=1,r,tr[25005];
long long f[30][25005],now;
void add(int x,int k){
for(;x<=n;x+=(x&-x))tr[x]+=k;
}
int query(int x,int ans=0){
for(;x;x-=(x&-x))ans+=tr[x];
return ans;
}
long long calc(int gl,int gr){
while(l>gl)l--,now+=query(n)-query(a[l]),add(a[l],1);
while(r<gr)r++,now+=query(a[r]),add(a[r],1);
while(l<gl)add(a[l],-1),now-=query(n)-query(a[l]),l++;
while(r>gr)add(a[r],-1),now-=query(a[r]),r--;
return now;
}
void solve(int l1,int r1,int l2,int r2,int p){
if(l2>r2)return;
int mid=(l2+r2)>>1,pos=mid;
for(int i=l1;i<=min(mid,r1);i++)if(f[p-1][i-1]+calc(i,mid)<f[p][mid])f[p][mid]=f[p-1][i-1]+calc(i,mid),pos=i;
solve(l1,pos,l2,mid-1,p),solve(pos,r1,mid+1,r2,p);
}
int main(){
memset(f,0x3f,sizeof(f)),f[0][0]=0,cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=k;i++)solve(1,n,1,n,i);
return cout<<f[k][n]<<'\n',0;
}
二分队列
对于 1D/1D 方程,由于是同一层之间转移,如果要分治,需要在外面再套一层分治,复杂度变成 \(O(n\log^2 n)\),不好。
从前往后,维护一个队列,队列里存三元组 \((p,l,r)\) 表示当前可用的决策点,\(f_p\) 转移到 \([l,r]\)。
若当前转移 \(i\),先将队尾 \(r<i\) 的出队。此时队尾就是转移 \(i\) 的决策点。
然后入队 \(i\)。如果队首的决策点严格劣于 \(i\),也就是 \(i\) 转移到 \(l\) 比 \(p\) 转移到 \(l\) 优,则出队。
入队时要求出 \(i\) 转移的范围,也就是二分出 \(l\),满足 \(l\) 之前从队首转移更优, \([l,n]\) 由 \(i\) 转移更优。然后将队首的 \(r\) 改为 \(p-1\),入队 \((i,l,n)\)。注意这里二分的边界为 \(l,n+1\),如果 \(i\) 不能转移就不入队。
\(sum_i=i+\sum_{j=1}^i len_j\),则 \(w(i,j)=|sum_i-sum_j-L-1|^P,f_{i}=\min_{j=0}^{i-1}f_j+w(i,j)\)。\(f(x)=|x-L-1|^p\) 的导数单调递增,所以有决策单调性,二分队列即可。
#include<bits/stdc++.h>
using namespace std;
int t,n,y,a[100005],bp,fp;
long double x,f[100005],p[100005];
string s[100005];
struct node{
int p,l,r;
}q[100005];
long double qpow(long double a,int b,long double ans=1){
for(;b;b>>=1)b&1&&(ans*=a),a*=a;
return ans;
}
long double calc(int l,int r){
return qpow(abs(a[r]-a[l]-x-1),y);
}
void out(int n){
if(!n)return;
out(p[n]);
for(int i=p[n]+1;i<=n;i++)cout<<s[i]<<(i==n?'\n':' ');
}
int main(){
cin.tie(0),cin>>t;
while(t--){
cin>>n>>x>>y;
for(int i=1;i<=n;i++)cin>>s[i],a[i]=a[i-1]+s[i].size()+1;
bp=fp=1,q[1]=(node){0,1,n};
for(int i=1;i<=n;i++){
while(bp<fp&&q[bp].r<i)bp++;
p[i]=q[bp].p,f[i]=f[q[bp].p]+calc(q[bp].p,i);
while(fp>bp&&f[i]+calc(i,q[fp].l)<=f[q[fp].p]+calc(q[fp].p,q[fp].l))fp--;
int l=q[fp].l,r=n+1;
while(l<r){
int mid=(l+r)>>1;
if(f[i]+calc(i,mid)<=f[q[fp].p]+calc(q[fp].p,mid))r=mid;
else l=mid+1;
}
if(l<=n)q[fp].r=l-1,q[++fp]=(node){i,l,n};
}
if(f[n]>1e18)puts("Too hard to arrange");
else cout<<(long long)f[n]<<'\n',out(n);
puts("--------------------");
}
return 0;
}
[[动态规划]]

浙公网安备 33010602011771号