luogu P3657 (NOIP2017) 跳房子(二分+DP+單調佇列)
阿新 • • 發佈:2018-11-06
題面
分析
顯然答案有單調性,可以二分答案,設當前二分值為g,根據題意我們可以求出跳躍長度的範圍[l,r]
考慮DP
子狀態: dp[i]表示跳到第i個點時的最大和
狀態轉移方程 \(dp[i]=max(dp[i],dp[j]+a[i]) (j \in [1,n),x[i]-x[j] \in [l,r])\)
初始值:dp[0]=0 (把起點看成第0號點,權值和座標都為0)
直接轉移的時間複雜度是\(O(n^2)\)
由於此題資料水,\(O(n^2logn)\)可以卡過
(相信熱愛學習,不屑於打暴力的你一定會繼續往下看的)
觀察狀態轉移方程,發現滿足條件的j一定在某個區間內,且區間在不斷移動,類似“滑動視窗問題”,可建立一個單調佇列
另外,j存在決策單調性,即i增加時,j一定也不斷增加,不會再減小(例如對於i=1時我們求出滿足條件的最大j,i=2時滿足條件的j一定比i=1時的j更大)
因此,每次迴圈時j不必重置成0,這樣可顯著減少時間
然後維護單調佇列
對於每個i,我們將滿足條件(與i距離>l)的dp[j]不斷加入隊尾,並同時維護序列的單調性,保證佇列從大到小遞減,這樣隊頭的答案一定最優
接著處理不合格的情況,即彈出隊頭與i距離>r的值
現在隊頭就是滿足\(j \in [1,n),x[i]-x[j] \in [l,r]\)
DP時間複雜度\(O(n)\)
總時間複雜度\(O(nlogn)\)
程式碼
暴力卡過:
#include<iostream> #include<cstdio> #include<cstring> #define maxn 500005 #define INF 0x3f3f3f3f3f3f3f3f using namespace std; inline int qread() { int x=0,sign=1; char c=getchar(); while(c<'0'||c>'9') { if(c=='-') sign=-1; c=getchar(); } while(c>='0'&&c<='9') { x=x*10+c-'0'; c=getchar(); } return x*sign; } int n,d; int k; int x[maxn]; int a[maxn]; long long dp[maxn]; int check(int g) { int l0,r0; memset(dp,-0x3f,sizeof(dp)); long long ans=dp[0]; if(g<d) { l0=d-g; r0=d+g; } else { l0=1; r0=d+g; } dp[0]=0; for(int i=1; i<=n; i++) { for(int j=i-1; j>=0; j--) { if(x[i]-x[j]<l0) continue; if(x[i]-x[j]>r0) break; if(dp[j]+a[i]>dp[i]) dp[i]=dp[j]+a[i]; if(dp[i]>=k) return 1; } if(dp[i]>ans) ans=dp[i]; } if(ans>=k) return 1; else return 0; } int main() { n=qread(); d=qread(); k=qread(); for(int i=1; i<=n; i++) { x[i]=qread(); a[i]=qread(); } dp[0]=0; x[0]=0; int l=0,r=1005; int mid; int ans=-1; int t; while(l<=r) { mid=(l+r)>>1; if(check(mid)) { ans=mid; r=mid-1; } else l=mid+1; } printf("%d\n",ans); } //Dedicated to Selina
單調佇列:
#include<iostream> #include<cstdio> #include<cstring> #define maxn 500005 #define INF 0x3f3f3f3f3f3f3f3f using namespace std; int n,d; int k; int x[maxn]; int a[maxn]; long long dp[maxn]; struct node{ long long v; int x; node(){ } node(long long val,int pos){ v=val; x=pos; } }; struct deque{ int head,tail; node Q[maxn]; node front(){ return Q[head]; } node back(){ return Q[tail-1]; } void push_back(node p){ Q[tail]=p; tail++; } void pop_front(){ head++; } void pop_back(){ tail--; } bool empty(){ if(head<tail) return 0; else return 1; } deque(){ head=tail=0; } void clear(){ head=tail=0; } }q; int check(int g){ int l0,r0; if(g<d) { l0=d-g; r0=d+g; } else { l0=1; r0=d+g; } int j=0; memset(dp,-0x3f,sizeof(dp)); q.clear(); dp[0]=0; for(int i=1;i<=n;i++){ while(x[i]-x[j]>=l0){//由於j有決策單調性,不必清零 while(!q.empty()&&dp[j]>=q.back().v) q.pop_back();//保證序列單調遞減 q.push_back(node(dp[j],x[j])); j++; } while(!q.empty()&&x[i]-q.front().x>r0) q.pop_front();//排除不符合條件的情況 if(q.empty()) dp[i]=-INF;//如果佇列為空,說明該點不能到達,直接設為-INF else dp[i]=q.front().v+a[i]; if(dp[i]>=k) return 1; } return 0; } int main(){ scanf("%d %d %d",&n,&d,&k); for(int i=1;i<=n;i++){ scanf("%d %d",&x[i],&a[i]); } x[0]=0; int l=0,r=x[n]; int mid; int ans=-1; while(l<=r){ mid=(l+r)>>1; if(check(mid)){ ans=mid; r=mid-1; }else l=mid+1; } printf("%d\n",ans); } //Dedicated to Selina