1. 程式人生 > >[noip 2017]普及組 T4 跳房子

[noip 2017]普及組 T4 跳房子

題目描述

跳房子,也叫跳飛機,是一種世界性的兒童遊戲,也是中國民間傳統的體育遊戲之一。

跳房子的遊戲規則如下:

在地面上確定一個起點,然後在起點右側畫 n 個格子,這些格子都在同一條直線上。每個格子內有一個數字( 整數),表示到達這個格子能得到的分數。玩家第一次從起點開始向右跳, 跳到起點右側的一個格子內。第二次再從當前位置繼續向右跳,依此類推。規則規定:

玩家每次都必須跳到當前位置右側的一個格子內。玩家可以在任意時刻結束遊戲,獲得的分數為曾經到達過的格子中的數字之和。

現在小 R 研發了一款彈跳機器人來參加這個遊戲。但是這個機器人有一個非常嚴重的缺陷,它每次向右彈跳的距離只能為固定的 d。小 R 希望改進他的機器人,如果他花 g 個金幣改進他的機器人,那麼他的機器人靈活性就能增加 g, 但是需要注意的是,每次彈跳的距離至少為 1。 具體而言, 當g < d時, 他的機器人每次可以選擇向右彈跳的距離為 d-g, d-g+1,d-g+2, …, d+g-2, d+g-1, d+g; 否則( 當g ≥ d時),他的機器人每次可以選擇向右彈跳的距離為 1, 2, 3, …, d+g-2, d+g-1, d+g。

現在小 R 希望獲得至少 k 分,請問他至少要花多少金幣來改造他的機器人。

輸入輸出格式

輸入格式:

第一行三個正整數 n, d, k, 分別表示格子的數目, 改進前機器人彈跳的固定距離, 以及希望至少獲得的分數。 相鄰兩個數之間用一個空格隔開。

接下來 n 行,每行兩個正整數x_i, s_i,分別表示起點到第i個格子的距離以及第i個格子的分數。 兩個數之間用一個空格隔開。 保證x_i按遞增順序輸入。

輸出格式:

共一行,一個整數,表示至少要花多少金幣來改造他的機器人。若無論如何他都無法獲得至少 k 分,輸出-1。

輸入輸出樣例

輸入樣例#1: 
7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
輸出樣例#1:
2
輸入樣例#2: 
7 4 20
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2
輸出樣例#2:
-1

說明

【輸入輸出樣例 1 說明】

花費 2 個金幣改進後, 小 R 的機器人依次選擇的向右彈跳的距離分別為 2, 3, 5, 3, 4,3, 先後到達的位置分別為 2, 5, 10, 13, 17, 20, 對應 1, 2, 3, 5, 6, 7 這 6 個格子。這些格子中的數字之和 15 即為小 R 獲得的分數。

輸入輸出樣例 2 說明

由於樣例中 7 個格子組合的最大可能數字之和只有 18 ,無論如何都無法獲得 20 分

資料規模與約定

本題共 10 組測試資料,每組資料 10 分。

對於全部的資料滿足1 ≤ n ≤ 500000, 1 ≤ d ≤2000, 1 ≤ x_i, k ≤ 109, |si| < 105。 對於第 1, 2 組測試資料, n ≤ 10;

對於第 3, 4, 5 組測試資料, n ≤ 500

對於第 6, 7, 8 組測試資料, d = 1

有難度,二分+DP+單調佇列優化

但是 DP方程非常簡單

設 dp[i] 帶表靈活性為 g 時,到第 i 個的最大可得到值

dp[i]=max{dp[j]}+a[i].value (max(1,d-g)<=a[i].set-a[j].set<=d+g)

因為只有當靈活性為g時,值在最大的時候,大於要求的分數,才能證明g可行

所以就可以這樣暴力列舉

	for(int i=1;i<=n;i++){
    	dp[i]=-INF;
    	for(int j=0;j<i;j++)
    		if(max(1,d-g)<=a[i].set-a[j].set&&a[i].set-a[j].set<=d+g)
    			dp[i]=max(dp[i],dp[j]+a[i].value);
	}


可以發現

dp [ i ] 只會由 dp [ l - r ) 轉移

所以我們就可以只列舉 dp [ l - r ) 的最大值

然後就可以優化一下列舉量

	int l=0,r=0;
	for(int i=1;i<=n;i++){
    	dp[i]=-INF;
    	while(a[i].set-a[r].set>=max(1,d-g))
			r++;
    	while(a[i].set-a[l].set>d+g)
			l++; 
    	for(int j=l;j<r;j++)
    		dp[i]=max(dp[i],dp[j]);
    	dp[i]+=a[i].value;
	}

dp [ i ]是由dp[ l - r ) 轉移過去的

而dp[ l - r )又會由於 i 的增加 向右移

又因為區間內資料的左邊小於它的資料相當於沒有什麼價值了

所以就可以寫一個單調佇列來維護單調佇列中下降的序列

我們就能在轉移之前就可以很容易地處理出該區間移動後的最大值是多少

然後,就是AC程式碼了!

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int n,d,k;long long ans;
long long dp[500005];
int a[500005][2];

struct zy{
	ll a;int b;
}dl[500005];

int DP(int g){
	int l=0,r=0;
	int head=0,tail=0;
	for(int i=1;i<=n;i++){
		while(a[i][0]-a[r][0]>=max(d-g,1)){
			while(head<tail&&dl[tail-1].a<dp[r])
				tail--;
    		dl[tail].a=dp[r];
    		dl[tail++].b=r++;
		}
		while(a[i][0]-a[l][0]>d+g){
			if(head<tail&&dl[head].b==l)
				if(dl[head].a==dp[l])
					head++;
			l++;
		}
		if(head>=tail)
			dp[i]=(-1ll<<60)+a[i][1];
		else
			dp[i]=dl[head].a+a[i][1];
		if(dp[i]>=k)
			return true;
	}
	return false;
}

int half(int L,int R){
	while(L<R){
		int mid=(L+R)>>1;
		if(DP(mid))R=mid;
		else L=mid+1;
	}
	return R;
}

int main(){
	scanf("%d%d%d",&n,&d,&k);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i][0]);
		scanf("%d",&a[i][1]);
	}
	ans=half(0,a[n][0]-a[1][0]);
	if(ans!=a[n][0]-a[1][0])
		printf("%lld",ans);
	else printf("-1");
	return 0;
}