【NOIP2017】跳房子

博客現地址:https://www.cnblogs.com/huangzihaoal/p/11154115.html
這題我0分。
比賽時,我一眼出正解,哈哈,太水了!
這題不就是一個二分+DP+單調隊列嗎?
然而,細節決定成敗。
我錯了許多細節,就掛了。
我只考了0分。。。
首先,這題滿足一個條件:
保證g變大後,如果原來滿足條件,現在也會滿足條件;而如果原來不滿足條件,現在就有可能滿足條件。
g變小後,如果原來滿足條件,現在不一定會滿足條件;而如果原來不滿足條件,現在就一定不可能滿足條件。
所以,我們可以用二分找出最合適的g的值。
已知,0g109{0\leq g\leq 10^9},且跳躍的範圍是 Max(1,d-g) ~ d+g。
我們就可以列出狀態轉移方程了:Fi=max(Fj)+Si   (Xj+aXiXj+bXi){F_i=max(F_j)+S_i\space\space\space(X_j+a\leq X_i \bigwedge X_j+b\geq X_i)}
其中,a爲跳躍最短距離,b爲跳躍的最長距離。
這樣做的時間複雜度是O(log2109n2){O(log_210^9n^2)},很明顯會時超50分。
所以,我們就要把DP優化一下了。
我們很容易發現,狀態轉移方程中,對於不同的i,max(Fj){max(F_j)}的值可能是一樣的,但我們的程序卻會從一個較大的區間 上一次最後一個 找到的位置+1 ~ i-1 中找 ,這就是程序中最耗時的地方。
怎麼優化呢?
我們有多種優化方式,其中我推薦兩種:大根堆,還有單調隊列。大根堆碼量大,而單調隊列方便快捷,因此我比較喜歡用單調隊列。
queuei{queue_i}表示單調隊列的第i個元素,用head表示單調隊列中有效範圍內的第一個元素的下標,用tail表示單調隊列中有效範圍內的最後一個元素的下標。
單調隊列存的是元素的下標,即Fi{F_i}Xi{X_i}中的 i ,這樣能方便判斷。
由於這個單調隊列是遞減的(即第一個元素最大,第二個元素比第一個小,第三個比第二個小……最後一個是最小的),所以我們每次使用的最大值就是單調隊列中有效範圍內的第一個元素對應的值。
那麼我們的狀態轉移方程就可以變成這個樣子了:Fi=Fqueuehead+Si  (Xqueuehead+aXiXqueuehead+bXi){F_i=F_{queue_{head}}+S_i\space\space(X_{queue_{head}}+a \leq X_i \bigwedge X_{queue_{head}}+b \geq X_i)}

其中,a爲跳躍最短距離,b爲跳躍的最長距離。
這樣用起來是很方便的,但是,重點來了——
怎樣才能保持單調隊列的單調性(使單調隊列遞減)和有效性(使(Xqueuehead+aXiXqueuehead+bXi){(X_{queue_{head}}+a \leq X_i \bigwedge X_{queue_{head}}+b \geq X_i)})呢?
首先,我們每一次加入元素時,如果 (new是新加入的元素),也就是說這個樣子(越高的值越大):
在這裏插入圖片描述
許多人都會認爲要變成下面這個樣子:
在這裏插入圖片描述
第i個柱子上面的數字是X[queue[i]]的值。
由於新加入單調隊列的數,都是可以跳到第i個格子上的,即Xnew+aXiXnew+bXi{X_{new}+a\leq X_i\bigwedge X_{new}+b\geq X_i}
而X又是遞增的,所以Xnew+aXiXi+1Xi+2Xn{X_{new}+a\leq X_i\leq X_{i+1}\leq X_{i+2}\cdots X_n}
但是從5(Xnew{X_{new}})這個位置出發,能跳到的最遠距離絕對比4遠,所以當5不能跳到某一個地方時,4也絕對跳不到那個位置。所以4就沒用了。
因此我們可以把4刪掉(即tail-1),最後再把5加入,變成下面這個樣子:
在這裏插入圖片描述
有時候我們要刪除很多元素,如下面這個例子:
在這裏插入圖片描述
變成
在這裏插入圖片描述
我們就要用一個while循環來刪除F值小於等於F[new]的數。


但我們的queue[head]是會過期的(queue[head]跳不到第i個格子),這時我們的queue[head]就不能用了。
我們要刪掉queue[head],怎麼刪掉呢?直接head+1就好了。
最後一點,建議同學們把不能到達的點的F賦值爲-maxlongint!


#include<cstdio>
using namespace std;
#define maxlongint 1999999999
int f[500001],queue[500001],x[500001],s[500001];
int main()
{
	freopen("jump.in","r",stdin);
	freopen("jump.out","w",stdout);
	int n,d,k,l=0,r=1000000000,mid,i,j,t,ans=-1,maxx,minn,head,tail,last;
	bool bk,bz;
	scanf("%d%d%d",&n,&d,&k);
	for(i=1;i<=n;i++) scanf("%d%d",&x[i],&s[i]);
	l=0;r=1000000000;
	while(l<=r)
	{
		mid=(l+r)/2;
		maxx=d+mid;bk=false;bz=true;
		minn=d-mid;last=0;
		if(minn<1) minn=1;
		tail=head=1;
		queue[1]=0;
		for(i=1;i<=n;i++)
		{
			if(maxx>=x[i]&&minn<=x[i])
			{
				bz=false;
				break;
			}
		}
		if(bz)
		{
			l=mid+1;
			continue;
		}
		for(i=1;i<=n;i++)
		{
			f[i]=maxlongint;
			for(j=last+1;j<i;j++)
			{
				if(x[j]+minn>x[i]) break;
				if(x[j]+maxx<x[i]) continue;
				last=j;
				if(f[j]==maxlongint) continue;
				while(head<=tail&&f[queue[tail]]<=f[j]) queue[tail--]=0;
				queue[++tail]=j;
			}
			while(head<tail&&x[queue[head]]+maxx<x[i]) head++;
			if(x[queue[head]]+maxx<x[i]||x[queue[head]]+minn>x[i]) f[i]=maxlongint;
			else f[i]=f[queue[head]]+s[i];
			if(f[i]<maxlongint&&f[i]>=k)
			{
				bk=true;
				break;
			}
		}
		if(bk)
		{
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	printf("%d\n",ans);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章