單調隊列和斜率優化是屬於決策單調性的一種。而決策單調性是知足四邊形不等式的前提下,知足i+1-n的轉移點大於等於i的決策點。而基本實現方式是總體二分或者維護雙端隊列而且在雙端隊列上二分查找。node
通常來講,1D/1D的DP都能經過優化,在$O(nlogn)$的時間複雜度以內轉移完成。ios
例子:數據結構
1.$f[i]=min/max{f[j]+s[j,i]};$($s[i,j]$表示的是從j向i轉移的時候,$s[i,j]$的每一項只與$i$或$j$相關,而且$j$的選擇區間是連續的)優化
單調隊列!$O(n)$解決!spa
2.$f[i]=min/max{f[j]+s[j,i]}$;($s[i,j]$表示的是從j向i轉移的時候,$s[i,j]$的每一項只與$i$或$j$相關,$j$的選擇區間是知足必定性質的)blog
數據結構!$O(nlogn)$解決!隊列
3.$f[i]=min/max{f[j]+s[j,i]}$;($s[i,j]$表示的是從j向i轉移的時候,$s[i,j]$的每一項最多與$calc(i)\times calc(j)$相關,$calc(i),calc(j)$均有單調性,j的選擇區間是連續的)get
斜率優化!$O(n)$解決!string
4.$f[i]=min/max{f[j]+s[j,i]}$;($s[i,j]$表示的是從j向i轉移的時候,$s[i,j]$的每一項最多與$calc(i)\times calc(j)$相關,$calc(i),calc(j)$均不必定具備單調性,但j的選擇區間是連續的)it
斜率優化+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$轉移。這個東西,每道題證實不一樣,就不寫了。
分析:
題面描述很狗血,看了半天沒看懂...後來看了好幾遍纔看懂。若是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\n",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\n",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\n",q[i].l,q[i].r,q[i].p);puts(""); } if(f[n]>1e18)puts("Too hard to arrange"); else { printf("%.0Lf\n",f[n]); //print(n); } printf("--------------------\n"); } return 0; }
帶權二分很顯然,帶權二分就不在這裏講了。
$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\n",f[n]-1ll*l*k); }
其餘的例題,就不添加了,這兩道題應該差很少了...其餘的看Blog決策單調性的標籤去找就能夠了...
最後,順便把個人1D1D的DP優化課件放上來吧,可能還不夠好,可是作的還算是用心...