P3957 跳房子

題目描述

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

跳房子的遊戲規則以下:ios

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

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

如今小 R研發了一款彈跳機器人來參加這個遊戲。可是這個機器人有一個很是嚴重的缺陷,它每次向右彈跳的距離只能爲固定的 dd。小 R但願改進他的機器人,若是他花 g個金幣改進他的機器人,那麼他的機器人靈活性就能增長 g ,可是須要注意的是,每 次彈跳的距離至少爲 11 。具體而言,當 g<dg<d 時,他的機器人每次能夠選擇向右彈跳的距離爲 d-g,d-g+1,d-g+2,…, d+g-2 , d+g-1 , d+g ;不然(當 g \geq dgd時),他的機器人每次能夠選擇向右彈跳的距離爲 1 , 2 , 3 ,…, d+g-2 , d+g-1 , d+g優化

如今小 R但願得到至少 k 分,請問他至少要花多少金幣來改造他的機器人。spa

輸入輸出格式

輸入格式:3d

 

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

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

 

輸出格式:htm

 

共一行,一個整數,表示至少要花多少金幣來改造他的機器人。若不管如何他都沒法得到至少 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, 76 個格子。這些格子中的數字之和15 即爲小 R 得到的分數。

輸入輸出樣例 2 說明

因爲樣例中 7個格子組合的最大可能數字之和只有 18 ,不管如何都沒法得到20

數據規模與約定

本題共 10 組測試數據,每組數據 10 分。

對於所有的數據知足1 ≤ n ≤ 500000, 1 ≤ d ≤2000, 1 ≤ x_i, k ≤ 10^9, |s_i| < 10^5。

對於第 1, 2組測試數據, n ≤ 10

對於第3, 4, 5 組測試數據, n ≤ 500

對於第6, 7, 86,7,8 組測試數據, d = 1

解析:

(題外話,我能說這個題我好長時間理解錯誤麼,我將這道題理解成了玩家能夠從源點向右跳,跳到任何一個座標爲整數的位置,而本題要求:玩家每次都必須跳到當前位置右側的一個格子內

,而這些格子是提早給定的。記錄一下個人錯誤程序,能夠把它改爲另外一個題呢

//起點爲,玩家只能夠想右跳,跳到座標軸的整數點位置,只有個別位置有分數; 
#include<iostream>
using namespace std;
int a[1000000]={0},b[1000000];
int bo;
int l,h,t;
int n,d,k,maxx;
void  dfs(int x,int sc,int dist,int t){//sc爲到達x得到的分數,dist爲當前靈活度 
    if (sc>=k) {
    bo=1;/*cout<<sc<<" :";for(int i=0;i<=t;i++)cout<<b[i]<<" ";cout<<endl;*/return ; }
    for(int i=l;i<=h;i++){
        int xx=x+i;
        if (xx<=maxx){b[t+1]=xx;dfs(xx,sc+a[xx],dist,t+1);}
    }    
}
int main(){
    int s=0, x;
    cin>>n>>d>>k;
    for(int i=1;i<=n;i++){
    
        cin>>x;
        cin>>a[x];
        s+=a[x];
    }
    maxx=x;
    if (s<k) cout<<-1;
    else
        for(int i=0;i<=100000;i++){//i爲靈活性 
            bo=0;
            if(d>i)l=d-i;else l=1;
            h=d+i;
            dfs(0,0,i,0);    
             if (bo) {cout<<i;break;}
        }
    return 0;
}
View Code

解法1:深度優先搜索(過3個點)

#include<cstring>
#include<iostream>
using namespace std;
const int maxn=1000000;
int a[maxn][5]={0},b[maxn],f[maxn];
int bo;
int l,h,t,ans;
int n,d,k,maxx;
void  dfs(int x,int sc,int pos){//sc爲到達pos得到的分數
    if (sc>=k) {    bo=1;return ; }//獲得解 
    if(bo||x>n) return ;//越界或者已經獲得解 
    if (a[x+1][1]-pos>h) return ;//不能跳那麼遠 
    if(a[x+1][1]-pos<l) {dfs(x+1,sc,pos);return ;}//不能跳那麼近 
    if (f[x+1]<sc+a[x+1][2]){
f[x+1]=sc+a[x+1][2];
dfs(x+1,sc+a[x+1][2],a[x+1][1]);//走x+1這個點 dfs(x+1,sc,pos);//跳過點x+1
    }
else if(f[x+1]<sc){ dfs(x+1,sc,pos);return ;} }

int main(){ int s=0, x; cin>>n>>d>>k; for(int i=1;i<=n;i++){ cin>>a[i][1]>>a[i][2]; if (a[i][2]>0)s+=a[i][2]; } int lift=0,right=a[n][1],mid; ans=-1;//變量定義的位置必定要準,ans不能放在下面的while循環裏,不然會受最後結果的影響 if (s<k) cout<<-1; else{ while (lift<=right){//等號必需存在,有時正解就是lift=right的時候 mid=(lift+right)>>1; if(d>mid)l=d-mid;else l=1; h=d+mid; memset(f,-127,sizeof(f)); f[0]=0; bo=0;//標誌本次遞歸是否會有解,必定要放在while裏噢! dfs(0,0,0); if(!bo) lift=mid+1; else {ans=mid;right=mid-1;} } cout<<ans<<endl; } return 0; }

 

解法2:二分+動態規劃

二分很顯然,花的錢越多跳到範圍越廣,更容易知足條件。
因而咱們二分花多少錢,檢驗可否拿到k分。
f[i]表示跳到第i個格子的最高得分:f[i]=max(f[j])+a[i][2] (L<=a[i][1]-a[j][1]<=H)(若是直接使用動態規劃時間複雜度是O(n2),能過5個點,其他超時)

#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000000;
int a[maxn][5]={0},b[maxn];
long long f[maxn];//a[i][1]是距離,a[i][2]是值。 
int bo;
int l,h,t,ans;
int n,d,k,maxx,inf=-0X7f;
int dp(){
    f[0]=0;//源點設置爲0 
    for(int i=0;i<=n;i++){
        for(int j=0;j<i;j++){
            if(a[i][1]-a[j][1]>=l&&a[i][1]-a[j][1]<=h&&f[i]<f[j])//能夠跳到        
                f[i]=f[j];
        }
        f[i]=f[i]+a[i][2];
        if(f[i]>=k) return 1;    
    }    
    return 0;
}
int main(){
    int s=0, x;
    cin>>n>>d>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i][1]>>a[i][2];
        if (a[i][2]>0)s+=a[i][2];
    }
    int lift=0,right,mid;
    right=max(d,a[n][1]);
    ans=-1;//變量定義的位置必定要準,ans不能放在下面的while循環裏,不然會受最後結果的影響 
    if (s<k) cout<<-1;
    else{
        while (lift<=right){//等號必需存在,有時正解就是lift=right的時候 
            mid=(lift+right)>>1;
            if(d>mid)l=d-mid;else l=1;
            h=d+mid;
            memset(f,-127,sizeof(f));
            f[0]=0;
            bo=0;//標誌本次遞歸是否會有解,必定要放在while裏噢! 
            bo=dp();
        //    cout<<bo<<" "<<mid<<": ";
         //    for(int i=0;i<=n;i++)
        //        cout<<f[i]<<" ";
        //    cout<<endl;
            if(!bo) lift=mid+1;
            else {ans=mid;right=mid-1;}
        }
         cout<<ans<<endl;
     //    for(int i=0;i<n;i++)
        //    cout<<f[i]<<" ";
        }
    return 0;
}
View Code

 

解法3:二分+動態規劃+單調隊列

 

這個題做爲2017普及組的壓軸題,各類坑。真正的考察你們的綜合能力。

(1)數據類型的定義,求和的變量要定義爲long long(由於:1xi,k109)

(2)二分時右邊界right=max(d,a[n][1]);//好比d=100,a[n][1]=10 ,坑啊,要考慮全面,加上這一條就ac,不然只過了5個點呢!!!

(3)單調隊列的變形使用

考慮到f[i]老是從前面可跳區間的最大值跳過來,隨着i日後面走,這個區間也日後面走,若是採用單調隊列,速度會快不少,由於每一個元素只進隊出隊一次,時間複雜度爲O(n)。


可是咱們發現了一個問題,對於某個決策k,它可能不能更新i,可是它能更新j(j<i)。這樣就不能直接用根據大小或者沒法更新當前決策把k出隊。

換句話說:不能像普通的單調隊列優化同樣僅憑這個決策的值小於另外一個決策的值就將決策排除候選集合,由於咱們的可行決策區間,是從[L,H],一個決策的值很大,可是他不必定能夠更新他的下一個格子,一些較劣決策仍然可能成爲最優決策,這是咱們最須要注意的。

這裏咱們巧妙的對單調隊列進行了變形:

入隊:求f[i]前,先考慮j是否入隊(j<i),若是j到i的最短距離<l,則j不入隊,由於他對求i沒有幫助,不然將全部符合條件的j按單調隊列的規則(小於j的值出隊)入隊。而後求f[i].

出隊:若是隊首到i的距離大於H,則出隊。


//考慮到f[i]老是從前面可跳區間的最大值跳過來,隨着i日後面走,這個區間也日後面走,若是採用單調隊列,速度會快不少,可是須要用到雙端隊列來構造單調隊列,代碼相對複雜一點, 
#include<cstring>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=500010;
int a[maxn][3]={0};//a[i][1]是距離,a[i][2]是值。
long long f[maxn],q[maxn][3]; 
int bo;
int l,h,t,ans=-1;
int n,d,k,maxx;
long long s; 
const long long inf=-0X8080808080808080;
void init(){
	cin>>n>>d>>k;
	for(int i=1;i<=n;i++){
		cin>>a[i][1]>>a[i][2];
		if (a[i][2]>0)s+=a[i][2];//記錄全部的正整數之和 
	}
}
int dp(){
	memset(f,0x80,sizeof(f));
	f[0]=0;//源點設置爲0 
	memset(q,0,sizeof(q));
	int head=1,tail=0,j=0;//tail指向隊尾的最後一個元素,head指向隊首元素 
	for(int i=1;i<=n;i++){ //求出i後並不入隊,由於 
		while(a[i][1]-a[j][1]>=l&&j<i){ //巧:若是j到i的最短距離<l,則j不入隊,一直到知足條件爲止。 ,>h的在後面刪除 
			if(f[j]!=inf){
				while(head<=tail&&q[tail][2]<f[j]) tail--;//隊尾指針比j小 
				tail++;//j入隊
				q[tail][1]=a[j][1];
				q[tail][2]=f[j];			
			}
			j++;	
		} 
		while (a[i][1]-q[head][1]>h&&tail>=head)head++;//i到隊首的距離>h,則從隊首出隊。 
		if(head<=tail){
			f[i]=q[head][2]+a[i][2]; 
			if(f[i]>=k) return 1;
		}
	}	
	return 0;
}
int main(){
	init();
	int lift=0,right,mid;
	right=max(d,a[n][1]);//好比d=100,a[n][1]=10 ,坑啊,要考慮全面,加上這一條就ac,不然只過了5個點呢!!! 
	ans=-1;//變量定義的位置必定要準,ans不能放在下面的while循環裏,不然會受最後結果的影響 
	if (s<k) cout<<-1;
	else{
		while (lift<=right){//等號必需存在,有時正解就是lift=right的時候 
			mid=(lift+right)>>1;
			if(d>mid)l=d-mid;else l=1;
			h=d+mid;
			memset(f,-127,sizeof(f));
			f[0]=0;
			bo=0;//標誌本次遞歸是否會有解,必定要放在while裏噢! 
			bo=dp();
			if(!bo) lift=mid+1;
			else {ans=mid;right=mid-1;}
		}
	 	cout<<ans<<endl;
		}
	return 0;
}
相關文章
相關標籤/搜索