求助:WA第一个点

P3957 [NOIP2017 普及组] 跳房子

特判没有石头的情况
by pomelo_nene @ 2019-05-25 14:52:40


我是不是走错了(逃)
by pomelo_nene @ 2019-05-25 14:53:09


感觉数据错了
by KokiNiwa @ 2019-06-12 22:29:34


好巧,本题AC刚好一年,无聊翻自己的帖子发现忘记填坑了。下面发一下我当时做完了之后写的题解(没发到洛谷上)。 #### 题意: 给定长度为$10^5$的带权含坐标的点序列,从$0$位置向右每次隔$(d-g,d+g)$取点权($d$给定),权值和不少于$k$,求最小的$g$。 #### 程序1(50pt): 对$g$在$[0,max\{x_{i}-d\}]$上二分。判定:定义$f_{i}$为取前$i$点的最大和,且 $ f_{i}+s_{j}\to f_{j} ( j>i,x_{j}\in [x_{i}+(d-g),x_{i}+(d+g)] ) $ 交上去发现$T$了,一看,二分域太大。 ```cpp #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; const LL inf=1LL<<34; const int N=500000; int n,d,x[N+3]; LL k,s[N+3],f[N+3]; bool chk(int g){ for(int i=1;i<=n;i++) f[i]=-inf; f[0]=0; for(int i=0;i<=n;i++) if(f[i]!=-inf){ int j; for(j=i+1;x[j]<x[i]+(d-g)&&j<=n &&x[j]<=x[i]+(d+g);j++); for(;x[j]<=x[i]+(d+g)&&j<=n;j++) f[j]=max(f[j],f[i]+s[j]); } LL tmp=-inf; for(int i=1;i<=n;i++) tmp=max(tmp,f[i]); return tmp>=k; } int main(){ scanf("%d%d%lld",&n,&d,&k); for(int i=1;i<=n;i++) scanf("%d%lld",&x[i],&s[i]); x[0]=0; int l=0,r=x[n]-d,mid,ans=-1; while(l<=r){ mid=(l+r)>>1; if(chk(mid)){ ans=mid; r=mid-1; } else l=mid+1; } printf("%d",ans); return 0; } ``` #### 程序2(90pt): 考虑以保证对于每两点间距离能跳过去作为二分边界,交上去证明我错了。 ```cpp #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; const LL inf=1LL<<62; const int N=500000; int n,d,x[N+3]; LL k,s[N+3],f[N+3]; bool chk(int g){ for(int i=1;i<=n;i++) f[i]=-inf; f[0]=0; for(int i=0;i<=n;i++) if(f[i]!=-inf){ int j; for(j=i+1;x[j]<x[i]+(d-g)&&j<=n &&x[j]<=x[i]+(d+g);j++); for(;x[j]<=x[i]+(d+g)&&j<=n;j++) f[j]=max(f[j],f[i]+s[j]); } LL tmp=-inf; for(int i=1;i<=n;i++) tmp=max(tmp,f[i]); return tmp>=k; } int main(){ scanf("%d%d%lld",&n,&d,&k); for(int i=1;i<=n;i++) scanf("%d%lld",&x[i],&s[i]); int maxd=x[1]; for(int i=2;i<=n;i++) maxd=max(maxd,x[i]-x[i-1]); x[0]=0; int l=0,r=maxd,mid,ans=-1; while(l<=r){ mid=(l+r)>>1; if(chk(mid)){ ans=mid; r=mid-1; } else l=mid+1; } printf("%d",ans); return 0; } ``` #### 程序3(100pt): 思考一下,发现权值和不能达到$k$的等价条件是正权和小于$k$,于是可以在二分之前排除掉不合法的情况。 思考一下,发现负权点对于确定$g$的最大值是没有贡献的,于是考虑以保证能跳过每两个正权点间距离作为二分边界,再重构一下DP代码,交上去过了。 ```cpp #include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long LL; const LL inf=1LL<<62; const int N=500000; int n,d,x[N+3]; LL k,s[N+3],f[N+3]; bool chk(int g){ for(int i=1;i<=n;i++) f[i]=-inf; f[0]=0; for(int i=0;i<=n;i++) if(f[i]!=-inf){ for(int j=i+1;j<=n;j++){ if(x[j]<x[i]+(d-g)) continue; if(x[j]>x[i]+(d+g)) break; f[j]=max(f[j],f[i]+s[j]); if(f[j]>=k) return true; } } return false; } int main(){ scanf("%d%d%lld",&n,&d,&k); for(int i=1;i<=n;i++) scanf("%d%lld",&x[i],&s[i]); int pre=0,maxd=0; LL sum=0; x[0]=0; for(int i=1;i<=n;i++) if(s[i]>0){ sum+=s[i]; maxd=max(maxd,x[i]-x[pre]); pre=i; } if(sum<k){ printf("-1"); return 0; } int l=0,r=maxd,mid,ans=-1; while(l<=r){ mid=(l+r)>>1; if(chk(mid)){ ans=mid; r=mid-1; } else l=mid+1; } printf("%d",ans); return 0; } ``` #### 小结: DP是最简单的DP,二分也没有什么骚操作,就是边界这种东西…… 好像摸到一点意思:求最大和的时候,负权对于答案的最低要求没有贡献,所以二分上界是要确保能取到所有正权。 也就是说,“最优答案”和“合法答案/最低要求/搜索边界”需要的数据不太一样,经常会用贪心思路找边界。 (错了再改)
by Hansue @ 2020-05-28 17:57:34


呃,我的意思是这是我去年5.28写完了这题之后在cnblog上发的题解。
by Hansue @ 2020-05-28 17:58:46


|