决策单调性 & 四边形不等式
_XHY20180718_ · · 算法·理论
决策单调性是个非常有用的东西,他的优化功能甚至超出了 dp 的范围。
对于一个形如以下的 DP 式子:
若有对于
那么
对于
下面是证明:
设
对于
又因为:
所以有:
这表明通过
反过来也是一样的,反四边形不等式在
接下来看看应用:
P1912 [NOI2009]诗人小G。
首先容易列出转移方程:
设
容易看出四边形不等式。
一般来说,一个满足四边形不等式的 dp 式子,有两张算法优化:
分治:
这种方法一般要让
由于
原理就是先求出
inline long double calc(int j,int i);
void solve(int x,int y,int l,int r){
if(l>r)return;
int mid=l+(r-l>>1),z=x;
for(int i=x; i<=min(mid-1,y); ++i){
if(calc(i,mid)<calc(z,mid))z=i;
}f[mid]=calc(z,mid);lst[mid]=z;
solve(z,y,mid+1,r);
solve(x,z,l,mid-1);
}
接下来讲第二种方法:
三元组+双端队列:
我们一步步考虑可能的决策点
定义一个三元组:
即在所有当前考虑到的可能的决策点中 (
首先初始化,一般是:qu[L=R=1]={0,1,n}。
那么我们枚举决策点时,我们首先一定要先根据之前找到的最优决策点算出
怎么对整个序列的三元组们进行更新呢?
我们先把那些区间的左端点的最优决策点都是
一般可以这样写:
inline void ins(int x){
while(L<=R&&calc(x,qu[R].l)<=calc(qu[R].p,qu[R].l))--R;
if(L<=R&&calc(x,qu[R].r)<=calc(qu[R].p,qu[R].r)){
int l=qu[R].l,r=qu[R].r,mid,p=qu[R].p;
while(l<r){
mid=l+(r-l>>1);
if(calc(x,mid)<=calc(p,mid))r=mid;
else l=mid+1;
}
qu[R].r=r-1;
}
if(qu[R].r<n){
node t={x,qu[R].r+1,n};
qu[++R]=t;
}
}
signed main(){
qu[L=R=1]={0,1,n};
for(int i=1; i<=n; ++i){
f[i]=calc(lst[i]=qu[L].p,i);
if(qu[L].r==i)++L;
qu[L].l=i+1;ins(i);
}
}
P1912 [NOI2009]诗人小G
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
const long double inf=1e18,eps=5;
int T,n,m,Ln,P,a[N],pre[N];
string s[N],ans[N];
int lst[N],L,R;
long double f[N];
struct node{
int p,l,r;
}qu[N];
inline long double fpm(long double x,int y){
long double res=1;
for(;y;y>>=1,x*=x)
if(y&1)res*=x;
return res;
}
inline long double calc(int j,int i)
{return f[j]+fpm(abs(pre[i]-pre[j]+i-j-1-Ln),P);}
inline void insert(int x){int pos=n+1;
while(L<=R&&calc(x,qu[R].l)<=calc(qu[R].p,qu[R].l))pos=qu[R--].l;
if(L<=R&&calc(x,qu[R].r)<=calc(qu[R].p,qu[R].r)){
int l=qu[R].l,r=qu[R].r,mid;
while(l<r){
mid=l+(r-l>>1);
if(calc(x,mid)<=calc(qu[R].p,mid))r=mid;
else l=mid+1;
}
qu[R].r=r-1;pos=r;
}if(pos<=n)qu[++R]={x,pos,n};
}
signed main(){
cin>>T;while(T--){
cin>>n>>Ln>>P;
memset(pre,0,sizeof pre);
memset(lst,0,sizeof lst);
for(int i=1; i<=n; ++i)ans[i]="";
for(int i=1; i<=n; ++i)f[i]=inf+1;
for(int i=1; i<=n; ++i)
cin>>s[i],a[i]=s[i].size(),pre[i]=pre[i-1]+a[i];
qu[L=R=1]={0,1,n};
for(int i=1; i<=n; ++i){
f[i]=calc(lst[i]=qu[L].p,i);
if(qu[L].r==i)++L;
qu[L].l=i+1;insert(i);
}
if(f[n]>=inf+1){cout<<"Too hard to arrange\n";goto A;}
printf("%.0Lf\n",f[n]);m=1;
for(int i=n; i; i=lst[i],++m){
for(int j=lst[i]+1; j<i; ++j)
ans[m]+=s[j],ans[m]+=' ';
ans[m]+=s[i];
}
for(int i=m-1; i; --i)
cout<<ans[i]<<'\n';A:;
cout<<"--------------------\n";
}
return 0;
}
[POI2011] Lightning Conductor。
列出式子:
设
直接上分治即可,不过要记得分类讨论,弄两个函数就行了:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
int n,a[N];
long double f[N],g[N];
inline long double calc(int j,int i){return a[j]-a[i]+sqrt(1.0*abs(i-j));}
void solvef(int l,int r,int x,int y){
if(l>r)return;
int z=x,mid=l+(r-l>>1);
for(int i=x+1; i<=min(mid,y); ++i)
if(calc(i,mid)>=calc(z,mid))z=i;
f[mid]=calc(z,mid);
solvef(l,mid-1,x,z);
solvef(mid+1,r,z,y);
}
void solveg(int l,int r,int x,int y){
if(l>r)return;
int z=y,mid=l+(r-l>>1);
for(int i=y-1; i>=max(mid,x); --i)
if(calc(i,mid)>=calc(z,mid))z=i;
g[mid]=calc(z,mid);
solveg(l,mid-1,x,z);
solveg(mid+1,r,z,y);
}
signed main(){cin>>n;
for(int i=1; i<=n; ++i)cin>>a[i];
solvef(1,n,1,n);solveg(1,n,1,n);
for(int i=1; i<=n; ++i)
cout<<(int)ceil(max(f[i],g[i]))<<'\n';
return 0;
}
再来看一道题:
CF868F:Yet Another Minimization Problem。
老规矩,先列出转移方程:
设
二维转移式子,不是区间 DP 很明显我们可以考虑分治。
但是,上一道题和诗人小 G 我们的贡献都是
没有关系,观察分治的过程,我们发现一般情况下左端点只会一个一个动,每次递归时若先递归右区间会移动
再看看我们本来用桶的计算方式:
把这个区间的数用桶统计完后,每次一个桶内元素的数量增加
将两个性质结合起来,我们发现桶内元素数量的变化在移动一次端点时可以
最后来讲讲关于区间 dp,的决策单调性,观察我们的转移式:
区间 dp 转移式比较特殊,他并不是由一个
那我们该怎么办呢?
首先
设
即
那么一定有:
所以就可以直接优化了,每次会遍历一个
满足四边形不等式的函数类:
为了更方便地证明一个函数满足四边形不等式,我们有以下几条性质:
性质 1:若函数
性质 2:若存在函数
性质 3:设
性质 4:设
首先需要澄清一点,凸函数(Convex Function)的定义在国内教材中有分歧,此处的凸函数指的是下凸函数,即(可微时)一阶导数单调增加的函数。
例题:
P5574 [CmdOI2019] 任务分配问题
P4767 [IOI2000] 邮局 加强版