题意:
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;
}
}