決策單調性
決策單調性
單調隊列和斜率優化是屬於決策單調性的一種。而決策單調性是滿足四邊形不等式的前提下,滿足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)*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)*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)之內。
決策單調性:
滿足四邊形不等式。
設j1<j2<i1<i2
那麽滿足如果i1從j2轉移,那麽i2必定不可能從j1轉移。這個東西,每道題證明不同,就不寫了。
例題時間:
BZOJ1563: [NOI2009]詩人小G
分析:
題面描述很狗血,看了半天沒看懂...後來看了好幾遍才看懂。如果p=2那麽很顯然,這個完全可以斜率優化,但是p並不只等於2。暴力DP:f[i]=f[j]+(sum[i]-sum[j]-L)^p;
時間復雜度,O(n^2)鐵定過不去...
其實很簡單,我們可以證明:
當滿足j1<j2<i1<i2的時候,並且滿足j2向i1轉移,即:f[j2]+calc(i1,j2)<=f[j1]+calc(i1,j1),的時候,必定有f[j2]+calc(i2,j2)<=f[j1]+calc(i2,j1);
細節證明,就不寫了,用反證法,之後推出不等式不成立即可。
那麽,我們就知道了這個東西具有決策單調性,具體實現:
假如現在,隊列中為空,也就是沒有一個狀態被確定,那麽,所有點的最優轉移都從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; }
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\n",f[n]-1ll*l*k); }
其他的例題,就不添加了,這兩道題應該差不多了...其他的看Blog決策單調性的標簽去找就可以了...
決策單調性