BZOJ5424 燒橋計劃(單調隊列優化dp)

傳送門(vjudge)

解題思路

注意到 \(a_i\) 的範圍很小,是1000~2000之間,於是我們可以直觀感受到k一定不會特別大,推一下可以得出 k 最多大概在四五百左右,於是可以直接考慮 dp[i][j] 爲前 i 個數裏面選了 j 個分割點,且第 i 個數是分割點的最小代價。

轉移要分兩種情況討論:

  • sum[pre+1~i-1] 大於 m
  • sum[pre+1~i-1] 小於等於 m

可以用類似雙指針的東西來維護這個分割點。
隨着 i 的增大,分割點右移,會有第二種情況的點變成第一種情況。
第一種情況的點集可以只維護一個min值,不斷更新即可。
第二種情況的點集一開始用想用優先隊列加個log梭哈過去,結果發現90分,很難受,只能被迫改成單調隊列qwq(比較明顯,要是pre比你靠後並且dp值還比你小,那你就可以退役了)

AC代碼

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<iomanip>
#include<ctime>
#include<stack>
using namespace std;
inline int read(){
	int x=0,f=1;char c=getchar();
	while((c<'0'||c>'9')) {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x*f;
}
#define pii pair<int,int>
#define mp(a,b) make_pair(-a,b)
#define fi first
#define se second
const int maxk=305;
const int maxn=1e5+5;
deque<int> q[maxk];
int dp[maxn][maxk];
int l[maxk],a[maxn],d[maxn],minn[maxk];
int main(){
	int T=1;
	while(T--){
		int n=read(),m=read();
		for(int i=1;i<=n;i++){
			a[i]=read();
			d[i]=d[i-1]+a[i];
		}
		memset(l,0,sizeof(l));
		memset(minn,0,sizeof(minn));
		memset(dp,0x3f,sizeof(dp));
		dp[0][0]=0;
		for(int i=0;i<=300;i++) while(!q[i].empty()) q[i].pop_front();
		q[0].push_back(0);
		for(int i=1;i<=n;i++){
			for(int j=min(i,300);j>=2;j--){
				int lst=j-1;
				while(d[i-1]-d[l[lst]]>m){
					l[lst]++;
					if(dp[minn[lst]][lst]+d[i-1]-d[minn[lst]]>dp[l[lst]][lst]+d[i-1]-d[l[lst]]) minn[lst]=l[lst];
				}
				while(!q[lst].empty()&&q[lst].front()<l[lst]){
					q[lst].pop_front();
				}
				int res=dp[minn[lst]][lst]+d[i-1]-d[minn[lst]];
				if(!q[lst].empty()){
					res=min(res,dp[q[lst].front()][lst]);
				}
				dp[i][j]=res+j*a[i];
				while(!q[j].empty()&&dp[i][j]<dp[q[j].back()][j]) q[j].pop_back();
				q[j].push_back(i);
			}
			dp[i][0]=(d[i]>m?d[i]:0);
			dp[i][1]=(d[i-1]>m?d[i-1]:0)+a[i];
			while(!q[1].empty()&&dp[i][1]<dp[q[1].back()][1]) q[1].pop_back();
			q[1].push_back(i);
		}
		int ans=(d[n]>m?d[n]:0);
		for(int i=1;i<=n;i++){
			for(int j=1;j<=min(i,300);j++){
				ans=min(ans,dp[i][j]+(d[n]-d[i]>m?d[n]-d[i]:0));
			}
		}
		printf("%d\n",ans);
	} 
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章