BZOJ-5424: 烧桥计划(单调队列)

BZOJ-5424: 烧桥计划(单调队列)

题目链接

题解:

先考虑最暴力的\(dp\):设\(f[k][i]\)表示搞掉第\(1\sim i\)段,烧了\(k\)段的最小花费,设\(calc(x,y)=sum[x\sim y]\le M?0:sum[x\sim y]\),可以列出转移方程如下

\[f[k][i]=min(f[k-1][j]+calc(j+1,i))+k*a[i] (j<i) \]

这样时间复杂度是\(O(n^3)\)的,十分爆炸

考虑优化
首先发现题目中给出的\(1000 \le a[i]\le 2000\)。仔细想想,这表明\(k\)值最大不会太大
设最坏情况下取了\(k\),则此时一定是满足\(k*(k+1)/2*1000\le n*2000\)
(就是说不是你把\(n\)段桥都断了也比\(k\)段优)
这样算下来\(n\)最大的时候\(k\)也就是\(600\)的样子,\(O(n*k)\)就可以过了

但现在时间复杂度还是\(O(n^2k)\)的,考虑对于每个\(k\)的每个\(i\),如何快速计算此时的\(f[k][i]\)

这个时候就可以用单调队列优化了。
设当前是\(f[k][i]\),题目中\(M\)的限制(就是那个\(calc(x,y)\))就相当于把\(1\sim i\)段分成了两部分:

前半部分要计算中间的\(sum[x\sim y]\),后半部分不用
那么对于前半部分记一个最小值,后半部分维护递增的单调队列,\(dp\)时取两个最小的那个就可以做到\(O(1)\)转移了

细节不少,刚开始写感觉很迷,写着写着也就想明白了吧
注意\(k\)是没有单调性的,一定从\(1\)\(T\)全枚举一遍

\(sxz\)一起卡了波时间,惊奇地发现\(k\)最大居然只有\(152\)
(别问为什么这么准,二分试出来的)

代码:

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<queue>
#include<stack>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define qmax(x,y) (x=max(x,y))
#define qmin(x,y) (x=min(x,y))
#define mp(x,y) make_pair(x,y)
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
inline int read(){
	int ans=0,fh=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
		ans=ans*10+ch-'0',ch=getchar();
	return ans*fh;
}
const int maxn=1e5+100;
int n,m,mx,q[maxn],p[maxn],l,r,a[maxn],f[2][maxn];
int tot,mn,sum[maxn],Ans=0x7fffffff;
int main(){
//	freopen("nh.in","r",stdin);
//	freopen("zhy.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++) a[i]=read();a[++n]=0;
	for(int i=1;i<=n;i++) sum[i]=a[i]+sum[i-1];
	int k=0,o=0,lc;
	memset(f,0x3f,sizeof(f));
	f[0][0]=f[1][0]=0;
	int T=min(152,(int)sqrt(n*4));
	while(T--){
		l=1,r=1,k++,tot=0,mn=0,lc=0;
		o^=1,q[1]=p[1]=0;
		for(int i=1;i<=n;i++){
			int now=0x7fffffff;
			while(l<=r&&sum[i-1]-sum[q[l]]>m) l++;
			while(1){
				if(sum[i-1]-sum[lc]<=m) break;
				qmin(mn,f[o^1][lc]+sum[i-1]-sum[lc]-tot);
				lc++;
			}
			if(l<=r) qmin(now,p[l]);
			qmin(now,mn+tot);
			f[o][i]=(now+=k*a[i]);
			while(l<=r&&p[r]>=f[o^1][i]) r--;
			p[++r]=f[o^1][i],q[r]=i;
			tot+=a[i];
		}
		qmin(Ans,f[o][n]);
	}
	printf("%d\n",Ans);
	return 0;
}
posted @ 2019-03-09 19:10  nianheng  阅读(394)  评论(0编辑  收藏  举报