codevs P4645 修理草坪-DP优化-单调队列

在一年前赢得了小镇的最佳草坪比赛后,FJ变得很懒,再也没有修剪过草坪。现在,
新一轮的最佳草坪比赛又开始了,FJ希望能够再次夺冠。

然而,FJ的草坪非常脏乱,因此,FJ只能够让他的奶牛来完成这项工作。FJ有N
(1 <= N <= 100,000)只排成一排的奶牛,编号为1...N。每只奶牛的效率是不同的,
奶牛i的效率为E_i(0 <= E_i <= 1,000,000,000)。

靠近的奶牛们很熟悉,因此,如果FJ安排超过K只连续的奶牛,那么,这些奶牛就会罢工
去开派对:)。因此,现在FJ需要你的帮助,计算FJ可以得到的最大效率,并且该方案中
没有连续的超过K只奶牛。

用F[i]表示最后一个选择第i只奶牛的最大值。状态转移方程和边界是非常显然的:

F[i]=max{F[j]+sum[i]-sum[j+1]},i-k-1<=j<i-1

考虑优化,一眼看到单调队列,把sum[i]提出来,变成

f[i]=max{f[j]-sum[j+1]}+sum[i],i-k-1<=j<i-1

所以在算f[i]的时候,问题转化为求f[j]-sum[j+1]的最大值,这个询问区间是单调递增且定长的。

于是,先将F[ 1 ~ k ]求出(=sum[ i ]),然后把F[1~k-1]-sum[2~k]放到(push)单调队列中。

然后从i=k~n,对于每个i,先用单调队列O(1)的更新,然后push(i-1)注意不是 i !然后弹出i-k-1。

但是这样只能得90分。剩下的一个点是k=1的情况。这个时候,计算F[2]的时候,单调队列中为空,要特判。

丑陋的代码如下:

//codevs P4645
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 100010
#define ll long long
using namespace std;
ll sum[MAXN],f[MAXN],ans;
int n,k,x;
template<typename Tp>
struct mtc_queue{
	private:
		int q[MAXN],fp,rp,vsz;
		Tp v[MAXN];
	public:
		mtc_queue()
		{
			fp=1;rp=vsz=0;
			memset(v,0,sizeof(v));
		}
		inline void clear()
		{
			fp=1;rp=vsz=0;
		}
		inline bool empty()
		{
			return (rp+1==fp)?true:false;
		}
		inline void push(const Tp &val)
		{
			while(!empty()&&v[q[rp]]<=val) rp--;
			v[q[++rp]=++vsz]=val;
		}
		inline void pop(int pos)
		{
			if(q[fp]==pos) fp++;
		}
		inline Tp front()
		{
			return v[q[fp]];
		}
};
mtc_queue<long long> q;
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&x);
		sum[i]=sum[i-1]+x;
	}
	//f[i]=max{f[j]+sum[i]-sum[j+1]},i-k-1<=j<i-1
	for(int i=1;i<=k;i++)
	{
		f[i]=sum[i];
		if(i!=k) q.push(f[i]-sum[i+1]);
	}
	for(int i=k+1;i<=n;i++)
	{
//		for(int j=i-k-1;j<i-1;j++) f[i]=max(f[i],f[j]+sum[i]-sum[j+1]);
		if(!q.empty()) f[i]=q.front()+sum[i];
		else f[i]=sum[i]-sum[i-1];
		if(!q.empty()) q.pop(i-k-1);
		q.push(f[i-1]-sum[i]);
		ans=max(ans,f[i]);
	}
	printf("%lld\n",ans);return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值