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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章