Codeforces 1077(F1+F2) DP 單調佇列
阿新 • • 發佈:2018-11-17
題意:給你一個n個元素的陣列,從中選取x個元素,並且要保證任意的m個位置中必須至少有一個元素被選中,問選中元素的和最大可以是多少?
F1 n,m,x到200 F2 n,m,x到5000。
思路1:設dp[i][j]為選擇i位置的元素,並且包括i位置已經選擇了j個元素,所有選中元素的最大和。
那麼為了保證方案的合法性,只能從i-m+1的地方狀態轉移,dp[i][j]=max(dp[k][j-1])+a[i] (i-m+1<=k<i);,意思是找從i-m+1位置到i-1位置找已經選區j-1個元素中和最大的一個,在加上自己。
最後在n-m+1到i中找最大值就是答案,這些點因為已經被選取,所以保證了最終答案的合法性。
程式碼:
#include<bits/stdc++.h> using namespace std; long long dp[210][210],a[210]; int main(){ int n,m,x; scanf("%d%d%d",&n,&m,&x); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } int tmp=n-(m-1); if((tmp/m+(tmp%m!=0))>x){ printf("-1\n"); return 0; } int lim=1; for(int i=1;i<=n;i++){ for(int j=lim;j<=min(i,x);j++){ for(int k=max(0,i-m);k<i;k++){ dp[i][j]=max(dp[k][j-1]+a[i],dp[i][j]); } } if(i%m==0)lim++; } long long ans=0; for(int i=n-m+1;i<=n;i++) ans=max(ans,dp[i][x]); printf("%lld\n",ans); }
思路2:現在資料規模變大了,原來的做法可以卡成O(n3),過不了。仔細觀察狀態轉移過程,會發現有很多無用的狀態,比如dp[i][j-1]<dp[k][j-1] (i<k),dp[i][j-1]這個狀態肯定不會成為更新的跳板,於是我們就可以用單調佇列優化。
外層迴圈列舉選取的元素個數,內層迴圈列舉陣列的元素,用單調佇列的隊頭更新最優解,此時dp[i][j]表示已經選區了i個元素,第j個元素已經被選的最優解,單調佇列的隊頭出隊過程保證了方案的合法性。
程式碼:
#include<bits/stdc++.h> using namespace std; long long dp[2][5010],a[5010]; int q[100010],l=1,r=0; int main(){ int n,m,x; scanf("%d%d%d",&n,&m,&x); for(int i=1;i<=n;i++){ scanf("%lld",&a[i]); } int tmp=n-(m-1); if((tmp/m+(tmp%m!=0))>x){ printf("-1\n"); return 0; } memset(dp,-0x3f,sizeof(dp)); dp[1][0]=0; int now=0; for(int i=1;i<=x;i++){ l=1,r=0; q[++r]=0; for(int j=1;j<=n;j++){ while(l<=r&&q[l]+m<j)l++; dp[now][j]=dp[now^1][q[l]]+a[j]; while(l<=r&&dp[now^1][q[r]]<=dp[now^1][j])r--; q[++r]=j; } now^=1; } now^=1; long long ans=-1; for(int i=n-m+1;i<=n;i++) ans=max(ans,dp[now][i]); printf("%lld\n",ans); }
---恢復內容結束---