【Week13作业 E】TT的神秘任务3【单调队列优化dp】

题意:

TT 猫咖的生意越来越红火,人越来越多,也越来越拥挤。
为了解决这个问题,TT 决定扩大营业规模,但猫从哪里来呢?
TT 第一时间想到了神秘人,想要再次通过完成任务的方式获得猫咪。
而这一次,神秘人决定加大难度。
给定一个环,A[1], A[2], A[3], … , A[n],其中 A[1] 的左边是 A[n]。要求从环上找出一段长度不超过 K 的连续序列,使其和最大。
这一次,TT 陷入了沉思,他需要你们的帮助。
第一行一个整数 T,表示数据组数,不超过 100。
每组数据第一行给定两个整数 N K。(1 ≤ N ≤ 100000, 1 ≤ K ≤ N)
接下来一行,给出 N 个整数。(-1000 ≤ A[i] ≤ 1000)。
对于每一组数据,输出满足条件的最大连续和以及起始位置和终止位置。
如果有多个结果,输出起始位置最小的,如果还是有多组结果,输出长度最短的。


思路:

先求出前缀和sum[i],这里要将环拉直,也就是多出了n+1~n+k-1的部分。
令f[i]是以i为结尾的最大连续子段和,f[i]=sum[i]-min{sum[j]}, i-k<=j<=i-1。
发现min{sum[j]}可以使用单调队列来优化,通过维护一个大小为k的单调队列,将遍历的时间复杂度优化到常数。
取ans的时候要注意如果答案相同,取起始位置最小的和长度最短的。


总结:

一道单调队列优化dp,在dp的基础上通过单调队列来优化min{sum[j]},将时间复杂度O(n*k)优化到O(n)。


代码:

#include <iostream>
#include <list>
using namespace std;

int n,k,t;
int a[200010];
long long int sum[200010];
//令f[i]为以i为结尾的最大连续子段和
//f[i]=sum[i]-min{sum[j]} (i-k<=j<=i-1)
long long int ans=-1e9;
int bg,ed;
list<int> q;
int main()
{
	ios::sync_with_stdio(false);
	cin>>t;
	while(t--)
	{
		cin>>n>>k;
		for(int i=1; i<=n; i++)
			cin>>a[i];
		q.clear();
		ans=-1e9,bg=0,ed=0;
		sum[0]=0;
		for(int i=1; i<=n+k-1; i++)	//求前缀和
		{
			int index=i;
			if(i>n)
				index=i-n;
			sum[i]=sum[i-1]+a[index];
		}
		//q.push_back(0);
		for(int i=1;i<=n+k-1;i++)
		{
			while(!q.empty()&&sum[q.back()]>sum[i-1])
				q.pop_back();
			q.push_back(i-1);
			while(!q.empty()&&i-k>q.front())
				q.pop_front();
			if(ans<sum[i]-sum[q.front()])
			{
				ans=sum[i]-sum[q.front()];
				bg=q.front()+1;
				ed=i;
				if(q.front()+1>n)
					bg=q.front()+1-n;
				if(i>n)
					ed=i-n;
			}
			if(ans==sum[i]-sum[q.front()])	//答案相同取长度最短的
			{
				int newbg=q.front()+1,newed=i;
				if(newbg>n)
					newbg=newbg-n;
				if(newed>n)
					newed=newed-n;
				if(newbg>bg)
					continue;
				if(newed-newbg<ed-bg)
					bg=newbg,ed=newed;
			 } 
		}
		cout<<ans<<" "<<bg<<" "<<ed<<endl;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值