決策單調性,通常用於1d/1d。
就是說,對於任意\(a<b<c<d\),若滿足在c處轉移到b比a優,那麼在d處也滿足。
另外一種理解:轉移決策點單調不降。
證明:主要靠打表。
得到這個性質後,我們有兩種方法:
1、分治法
每次取分治區間的中點,並暴力在可行區間內找到它的最優轉移位置。
之後,根據這個位置,分治兩邊的位置,並縮小可行區間的範圍。
時間複雜度:\(O(nlogn)\)。
優點:好寫,好理解,並且找轉移位置是連續尋找的,有的問題會比較方便。
缺點:要求dp之間沒有依賴(即沒有計算順序的要求),比如多階段dp。
2、二分+雙端隊列法
我們按照順序進行dp,並維護每個位置當前狀態下的最優轉移位置。
當計算到\(i\)時:
首先,算出\(dp(i)\)。
其次,算出用它轉移的區間。根據定義,只要找到第一個用\(i\)轉移比當前更優的位置,那麼之後的都是由它轉移更優。
這個位置可以使用二分查找。
因此,我們需要實現後綴覆蓋,\(O(1)\)單點查詢。似乎不容易。
由於後綴覆蓋具有均攤性,我們考慮維護轉移位置相同的若干段。
維護一個隊列,每個節點記錄它對應的區間,和轉移位置。
在二分查找時,我們從隊尾反向遍歷,並在這個區間內二分查找,若找不到,則退出。否則,把這個從隊尾扔出。
若沒有完全覆蓋,則把這個區間縮小。最後,在隊尾加入當前區間。
注意:
1、在依次計算dp時,要把隊首沒用的彈出。
2、在二分查找時,左端點要和\(i+1\)取\(\max\)。
3、要特判\(i\)不能更新任何位置的情況。
優點:適用情況更普遍,比如詩人小G
缺點:細節較多,難以理解,相對難寫。
剛纔那題的代碼:
#include <stdio.h>
#include <string.h>
#include <math.h>
#define max(a,b) a>b?a:b
#define ld long double
ld dp[100010];
ld ksm(ld a,int b)
{
ld jg=1;
while(b>0)
{
if(b&1)
jg*=a;
a*=a;b=(b>>1);
}
return jg;
}
char zf[100010][31];
int he[100010],wz[100010],st[100010],l,p;
ld cal(int i,int j)
{
return dp[j]+ksm(fabs((he[i]-he[j]-1)-l),p);
}
struct SJd
{
int l,r,z;
SJd(){}
SJd(int L,int R,int Z)
{
l=L;r=R;z=Z;
}
};
SJd dl[200010];
int efcz(int l,int r,int x,int y)
{
while(l<r)
{
int m=(l+r)>>1;
if(cal(m,x)<cal(m,y))
r=m;
else
l=m+1;
}
return l;
}
int main()
{
int T,n;
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&l,&p);
for(int i=1;i<=n;i++)
{
scanf("%s",zf[i]);
he[i]=strlen(zf[i])+he[i-1]+1;
}
dl[0]=SJd(1,n,0);
for(int i=1,he=0,ta=1;i<=n;i++)
{
while(he<ta&&i>dl[he].r)
he+=1;
int j=dl[he].z,zd=false;
dp[i]=cal(i,j);wz[i]=j;
while(he<ta&&efcz(max(dl[ta-1].l,i+1),dl[ta-1].r+1,i,dl[ta-1].z)<=dl[ta-1].r)
ta-=1,zd=true;
if(!zd)continue;
int l=efcz(max(dl[ta].l,i+1),dl[ta].r,i,dl[ta].z);
if(l>dl[ta].l)
dl[ta++].r=l-1;
dl[ta++]=SJd(l,n,i);
}
if(dp[n]>1e18)
printf("Too hard to arrange\n");
else
{
printf("%.0Lf\n",dp[n]);
int u=n,m=0;
while(u>0)
{
st[m++]=u;
u=wz[u];
}
for(int i=m-1,la=0;i>=0;i--)
{
for(int j=la+1;j<=st[i];j++)
{
printf("%s",zf[j]);
if(j<st[i])printf(" ");
}
la=st[i];
printf("\n");
}
}
printf("--------------------");
if(T>0)printf("\n");
}
return 0;
}