Cut the Sequence POJ
阿新 • • 發佈:2018-12-13
思路:dp[i]表示前第i項的最小值。
很容易寫出狀態轉移方程:dp[i]=min(dp[j]+max(a[j+1],a[i])),j<=i;
優化的思想是:我們需要快速的在前面找到一個j,同時還要找出j+1到i之間的最大項是什麼,這樣就能快速實現,最直接的思路是求RMQ,但是這題可以用單調佇列。
用單調佇列維護一個遞減的序列,對於當前的a[i],先將隊尾小於或等於a[i]的元素刪掉,再把a[i]插入隊尾。比如拿sample中的標號4-8一段8 1 8 2 1來看,那麼佇列裡面存的標號就是6 7 8,對應的a[]的值就是8 2 1。
其實我們可以發現,在第6項以後的最值就是佇列裡存的編號7,換句話說,我們列舉一個j,那麼下一個最值就是j+1對應的。
單調佇列中的下一個元素就是後一段的最大值。
最後還要保證任何區間的和不超過m,這個只要保證佇列的第一項到最後一項之和都不超過m,那麼列舉的任何子區間都不會超過m。
程式碼:
#include<iostream> #include<cmath> #include<cstdio> #include<cstdlib> #include<string> #include<cstring> #include<algorithm> #include<vector> #include<map> #include<set> #include<stack> #include<list> #include<queue> #include <deque> const int maxn=1e5+9; typedef long long ll; ll n,m; ll dp[maxn]; ll a[maxn]; struct node { int index; ll val; }que[maxn]; ll min(ll i,ll j) { return i<j?i:j; } int main(int argc, char const *argv[]) { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); freopen("out.txt","w",stdout); #endif while(scanf("%lld%lld",&n,&m)!=EOF) { //sum[0]=0; bool f=0; for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); if(a[i]>m) f=1; //sum[i]=sum[i-1]+a[i]; } if(f==1) { printf("-1\n"); continue; } int head,tail,pos; head=tail=0,pos=1; ll s=a[1]; que[tail].val=dp[1]=a[1]; que[tail++].index=1; for(int i=2;i<=n;i++) { s+=a[i]; while(s>m&&pos<i) { s-=a[pos]; pos++; } while(head<tail&&que[tail-1].val<=a[i]) { tail--; } que[tail].val=a[i],que[tail++].index=i; while(que[head].index<pos&&head<tail) { head++; } dp[i]=dp[pos-1]+que[head].val; for(int j=head;j<tail-1;j++) { dp[i]=min(dp[i],dp[que[j].index]+que[j+1].val); } } printf("%lld\n",dp[n]); } return 0; }