决策单调性总结

决策单调性,通常用于\(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;
}
posted @ 2020-03-08 19:38  lnzwz  阅读(1100)  评论(0编辑  收藏  举报