[TJOI2011]书架

题目大意:

输入两个正整数n,m,在输入n个数,将这n个数分成连续的若干段,满足每一段之和不超过m,同时使得这若干组中,每组的最大值之和最小,输出这个最小值。

对于30%的数据,n ≤ 1,000。

对于100%的数据,n ≤ 100,000,hi ≤ 10,000,m ≤ 1,000,000,000。

30%做法:

没学过dp的童鞋可以移步到其他题目去了这道题并不那么适合你......

30%的数据,n<=1000,可以双重循环枚举更新,设a数组为题目中读入的数组,f【i】为考虑将前i本书都放下的最小花费,明显可以由前面的f【j】转移过来,且贡献是放放f【j】+max(a【k】(j+1<=k<=i))。 可以先做个前缀和,以便于O(1)判断i,和j之间的书的总量是否超过m,当然也可以从i往前扫,每次考虑完一个j就加上啊a【j】。复杂度O(n^2),完全不虚。

100%做法:

考虑在30%做法的基础上进行优化,首先我们可以发现一个很显而易见的特点,就是f数组的值是单调不下降的,因为能放下前i+1本书的方案都必然放下了前i本书,花费自然不会比前i本书的少。所以这就可以用一些奇奇怪怪的玄学方法或者数据结构维护dp。首先要普及一种单调队列求区间最小值的方法:比如对于一个长度为6的序列1 5 2 1 4 3,我们要求出每3个数的最小值,我们可以维护一个单调下降的序列同时维护它的下标:一开始序列为空,然后操作到1,

序列:  值:  1

          下标: 1

然后操作到 5,这时候从队尾往前扫,扫到第一个碰到大于它的数或队列为空时停止:

序列:  值:  5

          下标: 2

(5比1大,将1弹出队列,队列为空,然后将5加入队列)

操作到2,

序列:  值:  5     2

          下标: 2     3

操作到1:

序列:  值:  5     2     1

          下标: 2     3     4

操作到4,

4比1大,将1弹出,比2大,再将2弹出,当新加入的值无法弹出其他数并加入队列以后,还有一步很重要的操作,就是判断一下队头的数的下标是否有超出求值范围,若现在加入了第i个数,考虑的是连续k个数的最大值,那么这时要考虑的是第i-k+1个数到第i个数的最大值,也就是说如果队头的数的下标要小于i-k+1,就要弹掉,这时5的下标是2,而我们已经加入的第5个数,也就是考虑的是3到5的最大值,5的下标过小,所以可以弹掉。

序列:  值:  4

          下标: 5

操作到1:

序列:  值:  4     1

          下标: 5     6

这时,我们就发现,每在队列里加入一个数时,需要3步操作:

1.先从队列的尾部往前一直弹掉比新加的数小的数

2.将新加的数加入队尾

3.判断队头的数是否需要弹掉

这样,不难发现,当我们插入第i个数的时候,以第i个数为结尾的连续k个数的最大值便是当前队头的数。

那么,为什么这个时间复杂度是O(n)的呢?首先,“3.判断队头的数是否需要弹掉”这一步最多只会弹一个数,然后,虽然“1.先从队列的尾部往前一直弹掉比新加的数小的数”这个操作一步可能会弹掉很多个数,但是一个n个数的序列,一共才n个数进入单调队列,额每弹一个数队列里就少了一个数,所以虽然插入一个数可能会弹掉很多个数,可是弹掉数的总数是不会超过n的,所以这是一个O(n)的算法。

然后,这道题的dp就可以用单调队列+线段树维护dp了,我们考虑枚举到了求f【i】的值,由于f数组具有单调性,所以假设f【j】和f【k】都可以对f【i】进行贡献,且第k+1个数到第i个数的最大值和第j+1个数到第i个数的最大值相等,那我们只需要取j和k中下标较小的对i进行贡献,换句话说,我们只需要维护能贡献到i的每种到i的最大值不同的j的下标最小的那一个就可以了,然后用个单调队列维护,操作和上面类似,而线段树是用来维护每一个单调队列中的j的贡献,可以快速查询最小值。具体还有些细节,就留给读者们自己思考了。

                                            

                                                                              推荐番:《约会大作战》

上代码:


#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
struct dat
{int val,wei,ff;};
dat line[100005];
int tre[500005],book[100005],qian[100005],f[100005],n,m,h,t,head,tail,s,p,ma;
void jia(int wei,int val,int l,int r,int root)
{
	if (l==r){tre[root]=val;return;}
	int mid=(l+r)/2;
	if (wei<=mid)jia(wei,val,l,mid,root*2);
	else jia(wei,val,mid+1,r,root*2+1);
	tre[root]=min(tre[root*2],tre[root*2+1]);
}
int fid(int h,int t,int l,int r,int root)
{
	if (l==r){return tre[root];}
	if (h<=l && t>=r)return tre[root];
	int mid=(l+r)/2,mm=1000000007;
	if (h<=mid)mm=fid(h,t,l,mid,root*2);
	if (t>=mid+1) mm=min(mm,fid(h,t,mid+1,r,root*2+1));
	return mm;
}
int main()
{
	cin>>n>>m;
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&book[i]);
	}
	h=1;head=1;tail=0;
	while (t<n && s+book[t+1]<=m)
	{
	     t++;s+=book[t];qian[t]=1;ma=max(ma,book[t]);f[t]=ma;
	     while (tail>0 && book[t]>=line[tail].val)tail--;
	     tail++;line[tail].val=book[t];line[tail].wei=t;line[tail].ff=line[tail-1].wei;
	     jia(tail,f[line[tail-1].wei]+book[t],1,n,1);
	}
	for (int i=t+1;i<=n;i++)
	{
		s+=book[i];
		while (s>m){s-=book[h];h++;}
		qian[i]=h;
	}
	 for (int i=t+1;i<=n;i++)
	 {
	 	while (line[head].wei<qian[i] && head<=tail){line[head].wei=0;head++;}
	 	if (line[head].ff<qian[i]-1){line[head].ff=qian[i]-1;jia(head,f[qian[i]-1]+line[head].val,1,n,1);}
	 	while (tail>=head && book[i]>=line[tail].val){tail--;}
	 	tail++;line[tail].val=book[i];line[tail].wei=i;line[tail].ff=max(line[tail-1].wei,qian[i]-1);
	 	jia(tail,f[line[tail].ff]+book[i],1,n,1);
	 	f[i]=fid(head,tail,1,n,1);
	 }
	 cout<<f[n];
}









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值