【NOIP2017】跳房子 题解(单调队列优化线性DP)
前言:把鸽了1个月的博客补上
-----------------
题目大意:机器人的灵敏性为$d$。每次可以花费$g$个金币来改造机器人,那么机器人向右跳的范围为$[min(d-g,1),max(d+g,x[n])]$。每个点都有分数$w[i]$。问至少花费多少金币得到分数$k$?
首先,如果用$g$个金币能满足条件,那么$g+1$也能。显然我们要最大值最小,所以我们不妨二分$g$,来求得满足条件的$g$的最小值。
普通的dp应该还是比较好写的。可以拿60pts.
bool check(int g) { for (int i=1;i<=n;i++) f[i]=-0x3f3f3f3f;f[0]=0; int rpos=d+g,lpos=max(d-g,1); for (int i=1;i<=n;i++) { for (int j=i=1;j>=1;j--) { if (x[i]-x[j]<lpos) continue; if (x[i]-x[j]>rpos) break; f[i]=max(f[i],f[j]+w[i]); if (f[i]>=k) return 1; } } return 0; }
对于每一个i,都有一定的可取范围(l,r)。注意到l,r都是单调递增的,且dp方程为$f[i]=max(f[j])+w[i]$,我们可以尝试用单调队列优化。时间复杂度$O(n)$。
bool check(int g) { memset(q,0,sizeof(q)); memset(f,0x80,sizeof(f));f[0]=0; int l=1,r=0,j=0; int L=d-g,R=d+g; if (L<0) L=1; for (int i=1;i<=n;i++) { while(x[i]-x[j]>=L&&j<i) { if (f[j]!=neInf)//f[j]必须是已经更新过的 { while(l<=r&&f[q[r]]<=f[j]) r--;//把劣于f[j]的决策排除,因为无论如何都不可能选择它们 q[++r]=j;//入列 } j++; } while(l<=r&&x[i]-x[q[l]]>R) l++;//把不满足条件的排除 if (l<=r) f[i]=f[q[l]]+s[i];//队首一定是最优选择 } int num=neInf; for (int i=1;i<=n;i++) num=max(num,f[i]); if (num>=k) return 1; else return 0; }
代码:
#include<bits/stdc++.h> #define int long long using namespace std; const long long neInf=0x8080808080808080; int f[500005],n,d,k; int q[500005]; int x[500005],s[500005],sum,aleft,aright,ans; inline int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();} while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();} return x*f; } bool check(int g) { memset(q,0,sizeof(q)); memset(f,0x80,sizeof(f));f[0]=0; int l=1,r=0,j=0; int L=d-g,R=d+g; if (L<0) L=1; for (int i=1;i<=n;i++) { while(x[i]-x[j]>=L&&j<i) { if (f[j]!=neInf) { while(l<=r&&f[q[r]]<=f[j]) r--; q[++r]=j; } j++; } while(l<=r&&x[i]-x[q[l]]>R) l++; if (l<=r) f[i]=f[q[l]]+s[i]; } int num=neInf; for (int i=1;i<=n;i++) num=max(num,f[i]); if (num>=k) return 1; else return 0; } signed main() { n=read(),d=read(),k=read(); for (int i=1;i<=n;i++) { x[i]=read(),s[i]=read(); if (s[i]>0) sum+=s[i]; } aright=max(x[n],d); if (sum<k){ cout<<-1; return 0; } while(aleft<=aright) { int mid=(aleft+aright)>>1; if (check(mid)) ans=mid,aright=mid-1; else aleft=mid+1; } cout<<ans; return 0; }