[HNOI2008]玩具装箱-DP斜率优化-单调队列-学习笔记

luogu P3195 https://www.luogu.org/problem/show?pid=3195

BZOJ 1010 http://www.lydsy.com/JudgeOnline/problem.php?id=1010

状态转移方程是显然的:

f[i]=min{f[j]+(i-j-1+s[i]-s[j]-L)^2},0<=j
这是一个1D/1D的DP问题。不优化的话是O(n^2)的,肯定会TLE。

考虑优化。显然可以换成斜率优化一般形式:

斜率优化一般形式:f[i]=min{ai*bj+ci+dj}.//这里ai,ci都是一开始就能O(1)确定的,bj,dj都是算出f[j]后可以O(1)确定的
f[i]=min{-2(i+s[i])(j+s[j]+L+1)+(i+s[i])^2+(j+s[j]+L+1)^2+f[j]}  //这一步通过代数运算,把多项式弄成(只和i有关)*(只和j有关)+(只和i有关)+(只和j有关)的形式。
所以在这道题目中,ai=-2*(i+s[i]);
bj=i+s[i]+L+1;ci=(j+s[j])^2;
dj=(i+s[i]+L+1)^2+f[j];
然后考虑:对于要求的状态f [ i ],如果状态j比k更优(不妨设bj

ai*bj+ci+dj<=ai*bk+ci+dk
化简整理得到:
-ai<=(dk-dj)/(bk-bj).
这里把(dk-dj)/(bk-bj)记作Kjk(规定b小的在前面),因为K的形式是个斜率,由此可知可以把b,d分别看作横纵坐标。
即当-ai<=Kjk的时候,从状态j转移更优。
然后继续考虑:

如果bx=Kyz
如果存在-ap<=Kxy,则从状态x转移更优
否则若-ap>Kxy>=Kyz,则从状态z转移更优
综上,若bx=Kyz,那么无论什么时候,都不会有从y转移最优的情况(注意这里并没有用到-ap)。因此可以把y删除。
所以我们要维护一些点,使得相邻的斜率时递增的。也就是一个下凸包。

一般意义上的下凸包维护起来比较麻烦,所以还要具体分析(见下文)。

那么如何找到最优转移状态呢?

考虑(由于K已经是递增的了):正在计算的状态是i,且Kxy<-ai<=Kyz,其中xyz是相邻的。显然因为K是单调递增的,这样的不等式最多只有一个。由左边的等号知:从状态y转移更优。由右边的等号知:从状态y转移更优。
于是我们就是在一个单调递增的数列中找-ai的bound(分不清是upper还是lower),这个总复杂度是O(nlgn)的。

还要注意的是有可能这个等会不存在的情况,需要特判。

当然也不是绝对的,比如说这道题,总复杂度可以做到O(n).

对这道题的具体分析:

首先,bj=j+s[j]+L+1是单调递增的。所以我们每次只是在右边加点,可以用一个单调栈维护。

每个元素进栈出栈最多各一次,总复杂度是O(n)的

其次-ai=2*(i+s[i])是单调递增的。即我们询问的值也是单调递增的。所以在序列左端可以维护一个指针,单调移动。

综上可以维护一个单调队列,左端支持删除,右端支持插入即可。

算法的总复杂度就是O(n).

附上代码,有一些特判比较丑陋。另外,这个题的数据比较大,所以用longlong会超界。

在判断两个斜率相称的时候只能用double类型通过除法来判断。(为此WA了十几次)

堵上丑陋的代码:

#include
#include
#include
#define debug(x) cerr<<#x<<'='<1.0) return 1;
	else return -1;
}
inline int kcmp(ll k,ll x1,ll y1,ll x2,ll y2)
{
	double s=(double)k/(y2-y1)*(x2-x1);
	if(s>1.0) return 1;
	else return -1;
}
struct Deque{
	private:
		ll qx[MAXN+10];
		ll qy[MAXN+10];
		int fp,rp;
	public:
		Deque()
		{
			memset(qx,0,sizeof(qx));
			memset(qy,0,sizeof(qy));
			fp=1;rp=0;
		}
		int push(ll x,ll y)
		{
			if(fp==rp||fp==rp+1)
			{	rp++;qx[rp]=x;qy[rp]=y;return 0;	}
			while(fp!=rp&&kcmp(qx[rp-1],qy[rp-1],qx[rp],qy[rp],x,y)>=0)//kxy>=kyz
				rp--;
			rp++;qx[rp]=x;qy[rp]=y;return 0;
		}
		ll query(ll k,ll ci)//k=-ai
		{
			ll bj,dj;
			if(fp==rp+1)
			{
				bj=L+1;dj=(ll)(L+1)*(L+1);
				return -k*bj+ci+dj;
			}
			if(fp==rp)
				return min(-k*qx[fp]+ci+qy[fp],-k*(L+1)+ci+(ll)(L+1)*(L+1));
			while(fp!=rp&&kcmp(k,qx[fp],qy[fp],qx[fp+1],qy[fp+1])>0)//k>kxy
				fp++;
			return min(-k*qx[fp]+ci+qy[fp],-k*(L+1)+ci+(ll)(L+1)*(L+1));
		}
}deq;
int main()
{
	scanf("%d%d",&n,&L);
	for(int i=1;i<=n;i++)
	{
		int input_number;
		scanf("%d",&input_number);
		s[i]=s[i-1]+input_number;
	}
	ll ai,ci,bj,dj,ans;
	for(int i=1;i<=n;i++)
	{
		ai=-2*(i+s[i]);
		ci=(i+s[i])*(i+s[i]);
		ans=deq.query(-ai,ci);
		bj=i+s[i]+L+1;
		dj=(i+s[i]+L+1)*(i+s[i]+L+1)+ans;
		deq.push(bj,dj);
	}
	printf("%lld\n",ans);
	return 0;
}


你可能感兴趣的:(斜率优化,学习笔记,单调队列)