P3957 跳房子

传送门

十分显然的DP

二分金币数量

然后对二分的金币数量跑DP求最大获利

方程:

  设 f [ i ] 表示在第 i 个格子里的最大获利

  f [ i ] = max( f [ j ] ) + val [ i ]  (pos[ j ]+max(1,m-g) ≤ pos[ i ] && pos[ j ]+m+g ≥ pos[ i ])

因为数据较大,我们要考虑优化

可以发现,转移只有在一段固定长度的区间中,而我们要的是这段区间的最大值

所以十分显然的单调队列优化 DP

然后就是一堆细节了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=5e5+7;
int n,m,t;
int pos[N],val[N];
ll f[N];//要开long long
int fir,las,q[N];
int L;//L是当前还未入队过的点的闭区间左端点
inline bool check(int p)//判断用p金币能否符合要求
{
    bool flag=0; int l=max(1,m-p),r=m+p;
    memset(f,128,sizeof(f)); f[0]=0;
    memset(q,0,sizeof(q));
    fir=0; las=0; L=1;//初始化
    for(int i=1;i<=n;i++)
    {
        while((pos[q[fir]]+r<pos[i])&&fir<=las) fir++;//把不符合条件的点踢出单调队列
        if(fir<=las&&pos[q[fir]]+l<=pos[i]) f[i]=f[q[fir]]+val[i];//注意判断合法性
        //后一个判断是因为初始q[fir]为0,0+m-g可能大于pos[i],除了0队列保证pos[q[j]]+m-g<=pos[i]
        if(f[i]>=t) { flag=1; break; }//可以在任意位置结束
        while(L<=i&&pos[L]+l<=pos[i+1])//把可以加入的点加入,另一个条件下一轮循环时会判断
        //现在不能判,要保证所有点都进过队列
        {
            while(f[q[las]]<=f[L]&&fir<=las) las--;//基本操作
            q[++las]=L; L++;//加入队列
        }
    }
    return flag;
}
int main()
{
    n=read(); m=read(); t=read();
    for(int i=1;i<=n;i++) pos[i]=read(),val[i]=read();
    int l=0,r=1e9,mid;
    while(l<=r)//二分
    {
        mid=l+r>>1;
        if(check(mid)) r=mid-1;//判断
        else l=mid+1;
    }
    printf("%d",l>1e9 ? -1 : l);
    return 0;
}
原文地址:https://www.cnblogs.com/LLTYYC/p/9864666.html