【poj3017】 Cut the Sequence

http://poj.org/problem?id=3017 (题目链接)

题意

  给出一个数列要求将它分割成许多块,每块的数的和不超过m,要求每块中最大的数之和最小。

Solution

  这道题真的很不错啊。

  可以很快写出dp方程:${f[i]=min(f[j]+max(a[j+1],a[j+2]···a[i]))}$。数据范围太大,我们必须要想办法优化这个方程。${O(n)}$的状态肯定是没办法优化到${O(1)}$了,想想怎么把转移优化到${O(logn)}$甚至${O(1)}$呢?似乎完全不可做的样子。

  终于还是膜拜了题解,发现:${f[i]}$一定是单调不降的。那么我们对于一个最大的数${a[k]}$,只要选择在它的管辖范围内尽可能靠前的点就好了。什么意思呢,就是:

设${a[j+1], a[j+2], ...a[i]}$中值最大的下标为${k}$

设${x}$为${[j+1,k]}$的任意一个下标,则${a[x],a[x+1],....a[i]}$的最大值的下标显然也是k了

由${f}$的非递减性,${f[j+1] + a[k] <= f[j+2]+a[k].....<= f[k - 1] + a[k]}$ 

很显然,我们只要取${f[j+1]+a[k]}$就可以了。

  于是我们用单调队列记录一个递减的序列${a[k]}$,因为队首不一定是最优,所以我们还要用平衡树记录${a[k]+f[j]}$来维护它。

  当当前${i}$到队首的距离超过m时,pop队首;当即将插入的元素大于队尾元素时,pop队尾。

细节

  multiset的运用。

代码

// poj3017
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<set>
#define LL long long
#define inf 2147483640
#define Pi acos(-1.0)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;

const int maxn=100010;
int q[maxn],a[maxn],n;
LL m,sum[maxn],f[maxn];

int main() {
	scanf("%d%lld",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	for (int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
	multiset<int> s;
	int k=0,l=1,r=0;
	for (int i=1;i<=n;i++) {
		if (a[i]>m) {printf("-1");return 0;}
		while (sum[i]-sum[k]>m) k++;
		while (l<=r && q[l]<=k)
			{if (l<r) s.erase(a[q[l+1]]+f[q[l]]);l++;}   //超过m
		while (l<=r && a[q[r]]<=a[i])
			{if (l<r) s.erase(a[q[r]]+f[q[r-1]]);r--;}   //当前a[i]比队尾元素大,pop队尾元素,并在平衡树中删除
		q[++r]=i;
		if (l<r && i>q[r-1])
			s.insert(a[i]+f[q[r-1]]);
		int tmp=*s.begin();
		f[i]=a[q[l]]+f[k];
		if (l<r && tmp<f[i]) f[i]=tmp;
	}
	printf("%lld",f[n]);
	return 0;
}

  

posted @ 2016-10-13 14:40  MashiroSky  阅读(1385)  评论(2编辑  收藏  举报