滑动窗口+二分--P3957 跳房子

* 思路:$dp_i$表示到第$i$个格子的最大得分,仔细思考后发现$f_i$只能从他之前一段区间内的最大$f_i$转移过来,且随着区域的后移不断改变,滑动窗口维护再二分答案就好了。

*代码实现:

  1. 滑动窗口的活动范围:$max$($d-len$,1)$\to$$d+len$
  2. 滑动窗口的移动:当最大值的位置超过了他能跳到的最远距离,则head++,窗口右移,另外如果我队尾的元素小于我现在加入的元素,那么队尾元素对答案没有贡献,弹出就好了,则tail--
    while (head<=tail&&dp[now]>=dp[q[tail]]) tail--;
          q[++tail]=now,now++;
    while (head<=tail&&x[q[head]]<x[i]-maxlen) head++;
  3. 注意如果当前点不能到达要直接跳过
    if (dp[now]<=-1e9) {now++;continue;}
  4. 二分答案:按升序的花费二分,如果当前花费无法满足总和大于$k$,那么前面的也一定不满足,则找右区间,如果当前花费满足总和大于$k$,那么后面的也一定满足,则找左区间,满足二分的单调性和局部舍弃性。
     while (l<r){
        int mid=(l+r)>>1;
        if (solve(mid)) tag=1,r=mid;
        else l=mid+1;
      }

完整代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #define int long long
 5 using namespace std;
 6 const int maxn=5e5+10;
 7 int n,d,k;
 8 int x[maxn],s[maxn],q[maxn],dp[maxn];
 9 bool tag=0;
10 bool solve(int len){
11   int now=0,head=1,tail=0,maxlen=len+d,minlen=max(d-len,1ll*1);
12   for (int i = 1;i <= n;i++){
13     while (x[now]<=x[i]-minlen){
14       if (dp[now]<=-1e9) {now++;continue;}
15       while (head<=tail&&dp[now]>=dp[q[tail]]) tail--;
16       q[++tail]=now,now++;
17     }
18     while (head<=tail&&x[q[head]]<x[i]-maxlen) head++;
19     if (head<=tail) dp[i]=dp[q[head]]+s[i];
20     else dp[i]=-1e9;
21     if (dp[i]>=k) return 1;
22   }
23   return 0;
24 }
25 signed main(){
26   scanf ("%lld%lld%lld",&n,&d,&k);
27   for(int i = 1;i <= n;i++) scanf("%lld%lld",&x[i],&s[i]);
28   int l=0,r=500000;
29   while (l<r){
30     int mid=(l+r)>>1;
31     if (solve(mid)) tag=1,r=mid;
32     else l=mid+1;
33   }
34   if (tag) printf("%lld\n",r);
35   else printf("-1\n");
36   return 0;
37 }

 

posted @ 2020-10-10 15:05  小又又  阅读(151)  评论(2编辑  收藏  举报