1. 程式人生 > >【NOIP2017普及組正式賽】跳房子

【NOIP2017普及組正式賽】跳房子

這一題很有它的價值,我都要認真思考一番才能夠想到方法。

先講一下題目大意

題目描述

       跳房子,也叫跳飛機,是一種世界性的兒童遊戲,也是中國民間傳統的體育遊戲之一。跳房子的遊戲規則如下:
      在地面上確定一個起點,然後在起點右側畫 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 行,每行兩個正整數�� �� , �� �� ,分別表示起點到第i個格子的距離以及第i個格子的分數。 兩個數之間用一個空格隔開。 保證�� �� 按遞增順序輸入。

輸出

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

樣例輸入

【輸入樣例 1】
7 4 10
2 6
5 -3
10 3
11 -3
13 1
17 6
20 2

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

樣例輸出

【輸出樣例 1】
2

【輸入輸出樣例 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】
-1

【輸入輸出樣例 2 說明】
由於樣例中 7 個格子組合的最大可能數字之和只有 18 ,無論如何都無法獲得 20 分。

資料範圍限制

本題共 10 組測試資料,每組資料 10 分。對於全部的資料滿足1 ≤ n ≤ 500000,1 ≤ d ≤2000,1 ≤ xi,k≤ 10^9 , |s i | < 10^ 5 。
對於第1,2 組測試資料,n ≤ 10;
對於第3,4,5組測試資料, n ≤ 500
對於第 6,7,8 組測試資料,d = 1

這一題有一點難了,不過,這畢竟是NOIP普及組第四題嘛!
正解——二分+單調佇列。
首先二分答案,然後就可以用一個單調佇列來做(記得要維持好單調佇列)
時間複雜度就不用說了,相信讀者們都知道(此題可過)。

上一下主要程式碼
二分答案部分:

        l:=0;//head
        r:=a[n,1]+d;//tail
        ans:=-1;//輸出的最小价值
        while l<=r do
        begin
                mid:=(l+r) div 2;//二分變數
                if pd(max(1,d-mid),mid+d) then//pd是用來判斷是否成立的,在下面的單調佇列有講
                begin
                        ans:=mid;
                        r:=mid-1;
                end else l:=mid+1;
        end;
        writeln(ans);

單調佇列(判斷能否得到k價值)部分:

function pd(zx,zd:longint):boolean;
var
        f,q:array[0..500000] of int64;
        i,j,head,tail,last:longint;
begin
        head:=1;
        tail:=1;
        last:=-1;//用來優化時間的
        q[1]:=0;
        f[0]:=0;
        for i:=1 to n do
        begin
                f[i]:=0;
                for j:=last+1 to i-1 do
                begin
                        if (a[j,1]+zx<=a[i,1]) and (a[j,1]+zd>=a[i,1]) then//判斷能否從a[j,1]跳到a[i,1]
                        begin
                                while (f[q[tail]]<f[j]) and (head<=tail) do
                                        dec(tail);//刪尾
                                inc(tail);
                                q[tail]:=j;
                                last:=j;
                        end;
                end;
                while ((a[q[head],1]+zd<a[i,1]) or (a[q[head],1]+zx>a[i,1])) and (head<=tail) do
                        inc(head);//移頭
                if (head>tail) then f[i]:=-maxlongint
                else f[i]:=f[q[head]]+a[i,2];//找到從零點到a[i,1]的最大獲得價值
                if f[i]>=k then exit(true);
        end;
        exit(false);
end;

還是上一下原始碼吧!

var
        a:array[0..500000,1..2] of longint;
        n,d,k,i,l,r,ans,mid:longint;
function max(x,y:longint):longint;
begin
        if x>y then exit(x)
        else exit(y);
end;
function pd(zx,zd:longint):boolean;
var
        f,q:array[0..500000] of int64;
        i,j,head,tail,last:longint;
begin
        head:=1;
        tail:=1;
        last:=-1;
        q[1]:=0;
        f[0]:=0;
        for i:=1 to n do
        begin
                f[i]:=0;
                for j:=last+1 to i-1 do
                begin
                        if (a[j,1]+zx<=a[i,1]) and (a[j,1]+zd>=a[i,1]) then
                        begin
                                while (f[q[tail]]<f[j]) and (head<=tail) do
                                        dec(tail);
                                inc(tail);
                                q[tail]:=j;
                                last:=j;
                        end;
                end;
                while ((a[q[head],1]+zd<a[i,1]) or (a[q[head],1]+zx>a[i,1])) and (head<=tail) do
                        inc(head);
                if (head>tail) then f[i]:=-maxlongint
                else f[i]:=f[q[head]]+a[i,2];
                if f[i]>=k then exit(true);
        end;
        exit(false);
end;
begin
        assign(input,'jump.in');reset(input);
        assign(output,'jump.out');rewrite(output);
        readln(n,d,k);
        for i:=1 to n do
                read(a[i,1],a[i,2]);
        l:=0;
        r:=a[n,1]+d;
        ans:=-1;
        while l<=r do
        begin
                mid:=(l+r) div 2;
                if pd(max(1,d-mid),mid+d) then
                begin
                        ans:=mid;
                        r:=mid-1;
                end else l:=mid+1;
        end;
        writeln(ans);
        close(input);
        close(output);
end.

這些地方都很容易理解,就是如何實現,因為那些細節真的是太多了!
(今年NOIP普及組沒能AK,真的是不幸中的不幸)