[luogu 3957]跳房子
50分做法
挺显然的一个做法,因为金币量是单调的(如果你花i枚金币可以得到最优解,i+1枚也一定可以),所以可以二分答案
然后对于二分出来的每个答案,都做一遍dp,效率$O(n^2logn)$
#include <cstdio> #include <cstring> using namespace std; #define N 500100 inline int read(){ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return x*f; } int n,d,k,a[N],s[N],dp[N]; int max(int x,int y){return x>y?x:y;} bool check(int g){ memset(dp,128,sizeof(dp)); int t1=(d-g)?d-g:1,t2=d+g,mx=0; dp[0]=0; for(int i=1;i<=n;i++){ for(int j=0;j<i;j++){ if(a[i]-a[j]>=t1&&a[i]-a[j]<=t2) dp[i]=max(dp[i],dp[j]+s[i]); } } for(int i=1;i<=n;i++)mx=max(dp[i],mx); if(mx>=k)return 1; return 0; } int main(){ n=read();d=read();k=read(); for(int i=1;i<=n;i++) a[i]=read(),s[i]=read(); int ans=0,l=0,r=10000000; while(l<=r){ int mid=(l+r)>>1; if(check(mid))r=mid-1,ans=mid; else l=mid+1; } if(ans)printf("%d\n",ans); else puts("-1"); return 0; }
100分做法
考虑怎么让效率降下来
50分的思路没问题,尝试一下能不能让每次dp的效率降下来
观察到答案其实也是单调的,dp[i]的答案是从前面i-d-g个数转移过来的,所以可以使用单调队列优化
总复杂度就变成$O(nlogn)$,能过100分的数据了
#include <cstdio> #include <cstring> #define ll long long inline ll read(){ ll x=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-f;c=getchar();} while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();} return x*f; } using namespace std; #define inf (1<<30) ll n,d,k,a[500010],s[500010]; ll dp[500010]; ll q[10000000]; ll max(ll x,ll y){return x>y?x:y;} bool check(ll g){ ll l=1,r=0,p=0,t1=max(d-g,1),t2=d+g; q[1]=0; for(int i=1;i<=n;i++){ dp[i]=-inf; while(a[i]-a[p]>=t1&&p<i){ while(l<=r&&dp[p]>=dp[q[r]])r--; q[++r]=p++; } while(a[i]-a[q[l]]>t2&&l<=r)l++; if(l>r||dp[q[l]]==-inf)continue; dp[i]=dp[q[l]]+s[i]; if(dp[i]>=k)return 1; } return 0; } int main(){ n=read(),d=read(),k=read(); for(int i=1;i<=n;i++) a[i]=read(),s[i]=read(); a[0]=0,s[0]=0; ll l=0,r=1000000000,ans=-1; while(l<=r){ ll mid=(l+r)>>1; if(check(mid))ans=mid,r=mid-1; else l=mid+1; } printf("%lld\n",ans); return 0; }