决策单调性
单调队列和斜率优化是属于决策单调性的一种。而决策单调性是满足四边形不等式的前提下,满足i+1-n的转移点大于等于i的决策点。而基本实现方式是整体二分或者维护双端队列并且在双端队列上二分查找。
1.基于1D/1D的DP优化
一般来说,1D/1D的DP都能通过优化,在$O(nlogn)$的时间复杂度之内转移完成。
例子:
1.$f[i]=min/max{f[j]+s[j,i]};$($s[i,j]$表示的是从j向i转移的时候,$s[i,j]$的每一项只与$i$或$j$相关,并且$j$的选择区间是连续的)
单调队列!$O(n)$解决!
2.$f[i]=min/max{f[j]+s[j,i]}$;($s[i,j]$表示的是从j向i转移的时候,$s[i,j]$的每一项只与$i$或$j$相关,$j$的选择区间是满足一定性质的)
数据结构!$O(nlogn)$解决!
3.$f[i]=min/max{f[j]+s[j,i]}$;($s[i,j]$表示的是从j向i转移的时候,$s[i,j]$的每一项最多与$calc(i) imes calc(j)$相关,$calc(i),calc(j)$均有单调性,j的选择区间是连续的)
斜率优化!$O(n)$解决!
4.$f[i]=min/max{f[j]+s[j,i]}$;($s[i,j]$表示的是从j向i转移的时候,$s[i,j]$的每一项最多与$calc(i) imes calc(j)$相关,$calc(i),calc(j)$均不一定具有单调性,但j的选择区间是连续的)
斜率优化+CDQ分治或者斜率优化+Splay维护动态凸包
5.$f[i]=min/max{f[j]+s[j,i]}$;($s[i,j]$表示的是从j向i转移的时候,$s[i,j]$满足四边形不等式,$j$的选择区间是连续的)
决策单调性
剩下的,大概我并不会了...但是上述的DP都可以优化到$O(nlogn)$之内。
决策单调性:
满足四边形不等式。
设$j_1<j_2<i_1<i_2$
那么满足如果$i_1$从$j_2$转移,那么$i_2$必定不可能从$j_1$转移。这个东西,每道题证明不同,就不写了。
例题时间:
BZOJ1563: [NOI2009]诗人小G
分析:
题面描述很狗血,看了半天没看懂...后来看了好几遍才看懂。如果p=2那么很显然,这个完全可以斜率优化,但是p并不只等于2。暴力DP:$f[i]=f[j]+(sum[i]-sum[j]-L)^p$;
时间复杂度,$O(n^2)$铁定过不去...
其实很简单,我们可以证明:
当满足$j_1<j_2<i_1<i_2$的时候,并且满足j2向i1转移,即:$f[j_2]+calc(i_1,j_2)le f[j_1]+calc(i_1,j_1)$,的时候,必定有$f[j_2]+calc(i_2,j_2)le f[j_1]+calc(i_2,j_1)$;
细节证明,就不写了,用反证法,之后推出不等式不成立即可。
那么,我们就知道了这个东西具有决策单调性,具体实现:
假如现在,队列中为空,也就是没有一个状态被确定,那么,所有点的最优转移都从0转移,也就是
0,0,0,0,0,0,0
现在,我们用0更新了1这个点,因为DP方程具有决策单调性,那么假如x点用1转移比用0转移更优,那么x到n的所有点都满足由1转移到0更优,而这个最小的x通过二分查找找到,将1压入队列。
0,0,1,1,1,1,1
假设,现在到达这个转移状态,我们继续用0更新了2,现在2进入了决策选择中,假如用2更新3不如用1更新,那么2的x点必定在5之后,也就是x后的某个通过二分查找找到的点,将2压入队列。
0,0,1,1,1,2,2
假设,现在到达这个转移状态,我们用1更新3,现在3进入的决策选择中,假如用3更新6比用2更新6更优,那么2就可以弹出队列,因为不存在一个点用2转移最优,之后再在1上二分查找x,之后更新状态。
以上就是决策单调性的题的一种实现方式。
直接附上代码,具体细节看代码即可
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstdlib> #include <cstring> using namespace std; #define N 100005 #define ll long double #define calc(x,y) (f[x]+q_pow(sum[y]-sum[x]-L,p)) struct node{int l,r,p;}q[N]; char s[35]; ll f[N],sum[N];int n,p,T,from[N],L; ll q_pow(ll a,int p){if(a<0)a=-a;ll ret=1;while(p){if(p&1)ret=ret*a;a=a*a,p=p>>1;}return ret;} int find(const node &a,int x) { int l=a.l,r=a.r+1; while(l<r) { int m=(l+r)>>1; // if(x==1)printf("%lld %lld %lld ",calc(x,m),calc(a.p,m),m); if(calc(x,m)>calc(a.p,m))l=m+1; else r=m; } return l; } void print(int i) { if(!i)return ; print(from[i]); for(int j=from[i]+1;j<i;j++)printf("%s ",s[j]); printf("%s ",s[i]); } int main() { scanf("%d",&T); while(T--) { scanf("%d%d%d",&n,&L,&p);L++;sum[0]=0;f[0]=0; for(int i=1;i<=n;i++)scanf("%s",s),sum[i]=sum[i-1]+strlen(s)+1; int h=0,t=0;q[t++]=(node){1,n,0}; for(int i=1;i<=n;i++) { if(q[h].r<i&&h<t)h++;f[i]=calc(q[h].p,i);from[i]=q[h].p; if(calc(i,n)<=calc(q[t-1].p,n)) { while(h<t&&calc(i,q[t-1].l)<=calc(q[t-1].p,q[t-1].l))t--; if(h==t)q[t++]=(node){i+1,n,i}; else{int x=find(q[t-1],i);q[t-1].r=x-1;q[t++]=(node){x,n,i};} } // for(int i=h;i<t;i++)printf("%d %d %d ",q[i].l,q[i].r,q[i].p);puts(""); } if(f[n]>1e18)puts("Too hard to arrange"); else { printf("%.0Lf ",f[n]); //print(n); } printf("-------------------- "); } return 0; }
BZOJ5311: 贞鱼
带权二分很显然,带权二分就不在这里讲了。
$f[i]=f[j]+calc(i,j)$;DP方程很显然,就长这样,显然calc(i,j)为那个矩形的权值和,而因此,可以直接得出$calc(i,j)<=calc(i,j-1)&&calc(i,j)<=calc(i+1,j)$;
附上代码:
#include <cstdio> #include <cmath> #include <algorithm> #include <iostream> #include <queue> #include <cstdlib> #include <cstring> using namespace std; #define N 4005 static char buf[1000000],*p1,*p2; #define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++) #define calc(x,y) (f[x]+((sum[y][y]+sum[x][x]-sum[y][x]-sum[x][y])>>1)) int rd() { register int x=0;register char c=nc(); while(c<'0'||c>'9')c=nc(); while(c>='0'&&c<='9')x=(((x<<2)+x)<<1)+c-'0',c=nc(); return x; } int sum[N][N],num[N],k,n,s[N][N];long long f[N]; struct node{int l,r,p;}q[N]; bool cmp(int i,int j,int k) { long long t1=calc(i,k),t2=calc(j,k); if(t1==t2)return num[i]<=num[j]; return t1<t2; } int find(const node &t,int x) { int l=t.l,r=t.r+1; while(l<r) { int m=(l+r)>>1; if(cmp(x,t.p,m))r=m; else l=m+1; } return l; } int check(int x) { memset(f,0x3f,sizeof(f)); f[0]=0;int h=0,t=0;q[t++]=(node){1,n,0};num[0]=0; for(int i=1;i<=n;i++) { if(q[h].r<i&&h<t)h++; f[i]=calc(q[h].p,i)+x;num[i]=num[q[h].p]+1; if(cmp(i,q[t-1].p,n)) { while(h<t&&cmp(i,q[t-1].p,q[t-1].l))t--; if(h==t)q[t++]=(node){i+1,n,i}; else { int p=find(q[t-1],i); q[t-1].r=p-1; q[t++]=(node){p,n,i}; } } } return num[n]; } int main() { n=rd();k=rd(); for(register int i=1;i<=n;i++) { for(register int j=1;j<=n;j++) { sum[i][j]=sum[i][j-1]+rd(); } } for(register int i=1;i<=n;i++) { for(register int j=1;j<=n;j++) { sum[i][j]+=sum[i-1][j]; } } int l=0,r=1<<30; while(l<r) { int m=(l+r)>>1; if(check(m)>k)l=m+1; else r=m; } check(l); printf("%lld ",f[n]-1ll*l*k); }
其他的例题,就不添加了,这两道题应该差不多了...其他的看Blog决策单调性的标签去找就可以了...
最后,顺便把我的1D1D的DP优化课件放上来吧,可能还不够好,但是做的还算是用心...