决策单调性总结
决策单调性,通常用于\(1d/1d\)。
就是说,对于任意\(a<b<c<d\),若满足在c处转移到b比a优,那么在d处也满足。
另外一种理解:转移决策点单调不降。
证明:主要靠打表。
得到这个性质后,我们有两种方法:
1、分治法
每次取分治区间的中点,并暴力在可行区间内找到它的最优转移位置。
之后,根据这个位置,分治两边的位置,并缩小可行区间的范围。
时间复杂度:\(O(nlogn)\)。
并且,它满足一个性质:若转移点为\((l,r)\),那么\(l,r\)的移动距离之和为\(O(nlogn)\),即均摊\(O(1)\),有时可以用类似莫队的方法。
优点:好写,好理解,并且找转移位置是连续寻找的,有的问题会比较方便。
缺点:要求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;
}