【NOIP2017】洛谷3957 跳房子题解(二分+DP+单调队列)

题目:luogu3957.
题目大意:现在给定一串长度为 n n n的序列 a a a a [ i ] a[i] a[i]表示第 i i i个元素的值.现在给定一个值 d d d,表示当使用 g g g枚金币时,可以每次向右移动 d + g d+g d+g~ d + g d+g d+g格(具体看题目),现在问你最少要用多少金币才能使跳到的格子的元素和至少为 k k k,若无解输出 − 1 -1 1.
1 ≤ n ≤ 5 ∗ 1 0 5 , 1 ≤ d ≤ 2000 , 1\leq n\leq 5*10^5,1\leq d\leq 2000, 1n5105,1d2000,.

这道题我们先来考虑无解的情况,若所有正数元素和都没有 k k k大,则输出 − 1 -1 1.

否则,我们进行暴力枚举 g g g,每次判定它是否能够使金币数大于 k k k.

很容易发现若 g g g可行,则 g + 1 , g + 2 , . . . , n g+1,g+2,...,n g+1,g+2,...,n就都可行,而如果 g g g不行,则 g − 1 , g − 2 , . . . , 1 g-1,g-2,...,1 g1,g2,...,1则都不行,这就满足了二分所需的单调性,所以考虑二分 g g g.

考虑用DP来判定,设 f [ i ] f[i] f[i]表示要跳到第 i i i个格子所需的最少金币数.那么方程如下:
f [ i ] = ( m a x x [ i ] − d − g ≤ x [ j ] ≤ m i n ( x [ i ] − d + g , x [ i ] − 1 ) f [ j ] ) + s [ i ] f[i]=(max_{x[i]-d-g\leq x[j]\leq min(x[i]-d+g,x[i]-1)}f[j])+s[i] f[i]=(maxx[i]dgx[j]min(x[i]d+g,x[i]1)f[j])+s[i]

发现这个DP貌似是求一个不断往右移的区间的最值的,貌似我们可以用什么东西维护?

我们选用单调队列来维护这个东西,就可以 O ( n ) O(n) O(n)DP了,总时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn).

代码如下:

#include
  using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=500000;
const int INF=(1<<30)-1;
int n,d,x[N+9],ans,s[N+9],f[N+9],k,h,t,q[N+9];
void clear(){h=1;t=0;}
bool empty(){return h>t;}
int top(){return q[h];}
void push(int x){
  while (h<=t&&f[x]>f[q[t]]) t--;
  q[++t]=x;
}
void pop(){h++;}
bool check(int g){
  for (int i=1;i<=n;i++) f[i]=-INF;
  clear();f[0]=0;
  int j=0;
  for (int i=1;i<=n;i++){
    while (j<i&&x[i]-x[j]>=d-g) push(j++);
    while (!empty()&&x[i]-x[top()]>d+g) pop();
    if (empty()||f[top()]==-INF) continue;
    f[i]=f[top()]+s[i];
    if (f[i]>=k) return true;
  }
  return false;
}
Abigail into(){
  scanf("%d%d%d",&n,&d,&k);
  for (int i=1;i<=n;i++)
    scanf("%d%d",&x[i],&s[i]);
}
Abigail work(){
  ans=INF;
  for (int i=29;i>=0;i--)
    if (check(ans-(1<<i))) ans-=1<<i;
}
Abigail outo(){
  if (ans==INF) printf("%d\n",-1);
  else printf("%d\n",ans);
}
int main(){
  into();
  work();
  outo();
  return 0;
}

你可能感兴趣的:(【NOIP2017】洛谷3957 跳房子题解(二分+DP+单调队列))