【洛谷P4756】Added Sequence
题目
题目链接:https://www.luogu.com.cn/problem/P4756
小\(L\)发明了一种新的数据结构,并将其命名为\(L\)数组。\(L\)数组的作用是可以在\(O(1)\)时间内将整个数组加上或减去一个数。现在给你一个长度为\(N\)的数组\(a\),他想用\(L\)数组来挑战你的计算能力。
定义\(f(i,j)=|\sum_{p=i}^{j} a_p|\)其中\(|x|\)表示\(x\)的绝对值。
定义一个数组的美丽度为\(\max_{1 \le i \le j \le N} f(i,j)\),每当他将整个数组加上\(x\) ,请你回答此时的美丽度。
注意,你的算法必须为在线的。
思路
显然答案等于变化后数组的前缀最大值减去前缀最小值。即使前缀最小值所在位置在前缀最大值后面,因为会套上一个绝对值,所以答案依然正确。
那么我们先把原数组 \(a\) 做前缀和,然后一个位置 \(i\) 加上 \(x\) 之后,它的前缀将全部加上 \(x\),那么就有 \(a_i=a'_i+ix\)。
发现这是一个一次函数,以 \(x\) 为横坐标,我们只需要维护出所有直线的上凸壳和下凸壳即可。
因为这些直线的斜率恰好就是 \(i\),所以当你加入第 \(i\) 条直线的时候,只需要判断它和单调栈顶第二个元素的交点横坐标与单调栈最顶上两个元素交点横坐标即可。画个图容易理解。
下凸壳同理。然后处理询问时二分一下 \(x\) 所在线段即可。
时间复杂度 \(O(n\log n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=200010;
int n,Q,top1,top2,st1[N],st2[N];
ll last,a[N];
double X(int i,int j)
{
return 1.0*(a[i]-a[j])/(j-i);
}
ll binary1(ll x)
{
int l=2,r=top1,mid;
while (l<=r)
{
mid=(l+r)>>1;
if (X(st1[mid],st1[mid-1])<=x) l=mid+1;
else r=mid-1;
}
return a[st1[l-1]]+x*st1[l-1];
}
ll binary2(ll x)
{
int l=2,r=top2,mid;
while (l<=r)
{
mid=(l+r)>>1;
if (X(st2[mid],st2[mid-1])<=x) l=mid+1;
else r=mid-1;
}
return a[st2[l-1]]+x*st2[l-1];
}
int main()
{
scanf("%d%d",&n,&Q);
for (int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
a[i]+=a[i-1];
}
for (int i=0;i<=n;i++)
{
while (top1>1 && X(i,st1[top1-1])<=X(st1[top1],st1[top1-1])) top1--;
st1[++top1]=i;
}
for (int i=n;i>=0;i--)
{
while (top2>1 && X(i,st2[top2-1])<=X(st2[top2],st2[top2-1])) top2--;
st2[++top2]=i;
}
while (Q--)
{
ll x;
scanf("%lld",&x);
x=(x+last)%(4*n+1)-2*n;
printf("%lld\n",last=(binary1(x)-binary2(x)));
}
return 0;
}