【單調佇列】【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)\)
需要注意的是,在單調佇列入隊時,只能檢查右端點是否符合要求,而不能檢查左端點是否符合要求。檢查左端點符合要求必須嚴格交給出隊時檢查,否則會導致入隊指標卡在一個位置。
即:在入隊時寫成
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
在單調佇列入隊時只能檢查右端點,出隊時只能檢查左端點。