传送门
其实这道题只要想清楚
dp循环变量的意义
以及dp所求的值
就非常简单了
解法:
设(i)点获得的分为(S_i)
先来考虑一个方面的问题
若我们知道(g) 即机器人性能的改变值
怎么求获得的最多的分
可以想到(dp)
设(dp[i])表示到i点为止可以获得的最大分
状态转移方程即为 (dp[i]=max_{j=0->i-1}{dp[j]}+S_i ext{条件:}(d-gle X_i-X_jle d+g))
(从0开始是因为可以从一开始位置就直接跳到(i)点)
p.s.可以发现其实并不需要枚举j,用单调队列维护即可。
这样我们就找到了知道(g)的情况下求获得最大分的方法
接下来就用2分找(g)就可以了
当然题目未给上界(有是有,但是为(10^9))
所以我们应先用倍增来求(g)的区间
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define inf 2000000000
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define dwn(i,a,b) for(int i=(a);i>=(b);--i)
using namespace std;
typedef long long ll;
int l,r;
int n,d,k,x[500010],s[500010];
ll dp[500010],sum=0;
int solve(int g)
{
memset(dp,-127,sizeof(dp));
int lp=max(1,d-g),rp=d+g;
ll ans=-inf;
dp[0]=0;
rep(i,1,n)
{
dwn(j,i-1,0)
{
if(x[i]-x[j]>rp) break;
if(x[i]-x[j]<lp) continue;
dp[i]=max(dp[i],dp[j]+s[i]);
}
ans=max(ans,dp[i]);
if(ans>=k) return ans;
}
return ans;
}
int main()
{
scanf("%d%d%d",&n,&d,&k);
rep(i,1,n)
{
scanf("%d%d",&x[i],&s[i]);
if(s[i]>0)sum+=s[i];
}
if(sum<k)
{
printf("-1
");
return 0;
}
r=10;
while(true)
{
if(solve(r)>=k) break;
r*=10;
}
l=r/10;
while(l<r)
{
int mid=(l+r)>>1;
if(solve(mid)>=k)
r=mid;
else
l=mid+1;
}
printf("%d
",l);
return 0;
}
单调队列优化:
int q[500010],ql,qr;
int solve(int g)
{
memset(dp,-127,sizeof(dp));
int lp=max(1,d-g),rp=d+g,cnt=0;
dp[0]=0;
ql=1,qr=0;
rep(i,1,n)
{
while(true)
{
while(ql<=qr&&cnt<i&&x[i]-x[cnt]>=lp&&dp[cnt]>=dp[q[qr]]) qr--;
if(cnt<i&&x[i]-x[cnt]>=lp) q[++qr]=cnt++;
else break;
}
while(ql<=qr&&x[i]-x[q[ql]]>rp) ql++;
if(ql<=qr) dp[i]=dp[q[ql]]+s[i];
if(dp[i]>=k) return dp[i];
}
return -inf;
}
但是由于这道题枚举时边界的特殊性
用单调队列优化并不能快多少
所以当做练练手写写吧