1. 程式人生 > >【單調佇列】【P3957】 跳房子

【單調佇列】【P3957】 跳房子

傳送門

Description

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

跳房子的遊戲規則如下:

在地面上確定一個起點,然後在起點右側畫 $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 \geq d$ 時),他的機器人每次可以選擇向右彈跳的距離為 $1$ , $2$ , $3$ ,…, $d+g-2$ , $d+g-1$ , $d+g$ 。

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

Input

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

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

Output

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

Hint

\(For~all:\)

\(1~\leq~n~\leq~500000~,~1~\leq~d~\leq~2000~,~1~\leq~x_i,k~\leq~10^9~,~|s_i|~\leq~10^5\)

Solution

這顯然是個滿足單調性的問題,於是考慮二分解決。

二分一個答案\(x\),那麼即求出花費\(x\)的金幣以後能得到的最大分數。考慮DP一波。

\(f_i\)為跳到第\(i\)個格子的答案。從前面列舉上一個位置轉移,時間複雜度\(O(n^2logn)\),期望得分\(50pts\)

考慮每一個\(i\)的轉移都是從同樣長的區間轉移來的,並且左端點單調不降。於是考慮使用單調佇列優化DP,時間複雜度將至\(O(nlogn)\)

,期望得分\(100pts\)

需要注意的是,在單調佇列入隊時,只能檢查右端點是否符合要求,而不能檢查左端點是否符合要求。檢查左端點符合要求必須嚴格交給出隊時檢查,否則會導致入隊指標卡在一個位置。

即:在入隊時寫成

while((pos < i) && ((MU[i].s-MU[pos].s) >= downfloor) && ((MU[i].s-MU[pos].s) <= upceil))

是不對的,因為最後一個條件檢查了左端點是否符合要求。

while((pos < i) && ((MU[i].s-MU[pos].s) >= downfloor))

才是正確的寫法。

Code

#include<cstdio>
#include<cstring>
#define rg register
#define ci const int
#define cl const long long

typedef long long ll;

template <typename T>
inline void qr(T &x) {
    rg char ch=getchar(),lst=' ';
    while((ch > '9') || (ch < '0')) lst=ch,ch=getchar();
    while((ch >= '0') && (ch <= '9')) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    if(lst == '-') x=-x;
}

namespace IO {
    char buf[120];
}

template <typename T>
inline void qw(T x,const char aft,const bool pt) {
    if(x < 0) {x=-x,putchar('-');}
    rg int top=0;
    do {IO::buf[++top]=x%10+'0';} while(x/=10);
    while(top) putchar(IO::buf[top--]);
    if(pt) putchar(aft);
}

template <typename T>
inline T mmax(const T a,const T b) {return a > b ? a : b;}
template <typename T>
inline T mmin(const T a,const T b) {return a < b ? a : b;}
template <typename T>
inline T mabs(const T a) {return a < 0 ? -a : a;}

template <typename T>
inline void mswap(T &_a,T &_b) {
    T _temp=_a;_a=_b;_b=_temp;
}

const int maxn = 500010;

int n,d,k,front,end;
int que[maxn];
ll frog[maxn];

struct M {
    int s,v;
};
M MU[maxn];

bool check(int x);

signed main() {
    qr(n);qr(d);qr(k);
    for(rg int i=1;i<=n;++i) {
        qr(MU[i].s);qr(MU[i].v);
    }
    int r=100001,l=0,ans=-1,mid;
    while(l <= r) {
        mid=(l+r)>>1;
        if(check(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    qw(ans,'\n',true);
    return 0;
}

bool check(ci x) {
    memset(frog,0,sizeof frog);
    que[front=end=1]=0;
    rg int upceil = d+x,downfloor=mmax(1,d-x);
    rg int pos=1;
    for(rg int i=1;i<=n;++i) {
        while((pos < i) && ((MU[i].s-MU[pos].s) >= downfloor)) {
            while((front <= end) && (frog[que[end]] <= frog[pos])) --end;
            que[++end]=pos++;
        }
        while((front <= end) && (((MU[i].s-MU[que[front]].s)  > upceil))) ++front;
        frog[i]=-1ll<<60;
        if(front > end) continue;
        if(MU[i].s-MU[que[front]].s < downfloor) continue;
        frog[i]=frog[que[front]]+MU[i].v;
        if(frog[i] >= k) return true;
    }
    return false;
}

Summary

在單調佇列入隊時只能檢查右端點,出隊時只能檢查左端點。