我们将 F(B,L,R)定义为整数数组 B 的索引从 L到 R(包括两者)的子数组的各个元素之和。
更具体的说,F(B,L,R)=+
+
…+
。
如果一个长度为 K的整数数组 C满足其所有前缀和均为非负整数,则称数组 C 为快乐数组。
更具体的说,如果 F(C,1,1),F(C,1,2),…,F(C,1,K)均为非负整数,则数组 C为快乐数组。
给定一个包含 N 个整数的数组 A,请你计算数组 A 中的所有快乐连续子数组的元素和相加的结果。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含整数 N。
第二行包含 N 个整数 A1,A2,…,AN。
输出格式
每组数据输出一个结果,每个结果占一行。
结果表示为 Case #x: y
,其中 x 为组别编号(从 11 开始),y为所有快乐连续子数组的元素和相加的结果。
数据范围
1≤T≤100,
−800≤Ai≤800,
每个测试点最多 30 组数据满足 1≤N≤4×105,其余数据满足 1≤N≤200。
输入样例:
2
5
1 -2 3 -2 4
3
1 0 3
输出样例:
Case #1: 14
Case #2: 12
样例解释
在 Case 1 中,满足条件的快乐连续子数组有 [1],[3],[3,−2],[3,−2,4],[4],它们的元素和分别为 1,3,1,5,4,相加得到结果 14。
在 Case 2 中,满足条件的快乐连续子数组有 [1],[1,0],[1,0,3],[0],[0,3],[3],它们的元素和分别为 1,1,4,0,3,3,相加得到结果 12。
思路
首先根据时间复杂度分析
30*4*1e5=1e7
所以时间复杂度要达到O(n)
首先,预处理算出数组的前缀和
根据单调栈求前缀和数组中右边第一个小于它的值
例如枚举数组的左端点为 i+1
那么首先根据单调栈求出数组右端点为 j
( i + 1 , i + 1 ),( i + 1 , i + 2),......( i + 1 , j - 1)
那么此时这个左端点所加的值应该为
w[i+1]*(j-(i+1))+w[i+2]*(j-(i+2))+...+w[j-1]*(j-(j-1))
发现他是一个等差数列
可以根据前缀和的扩展来求它的值
A[i]表示它的前缀和,A[i]=w[1]+w[2]+...+w[i]
B[i]表示前缀和与等差数列的乘积,B[i]=w[1]*(n-1)+w[2]*(n-2)+...+w[i]*(n-i)
B[j-1]-B[i]=w[i+1]*(n-(i+1))+w[i+2]*(n-(i+2))+...+w[j-1]*(n-(j-1))
(B[j-1]-B[i])-{w[i+1]*(j-(i+1))+w[i+2]*(j-(i+2))+...+w[j-1]*(j-(j-1))}
=w[i+1]*(n-j)+w[i+2]*(n-j)+...+w[j-1]*(n-j)=(A[j-1]-A[i])*(n-j)
所以w[i+1]*(j-(i+1))+w[i+2]*(j-(i+2))+...+w[j-1]*(j-(j-1))=(B[j-1]-B[i])-(A[j-1]-A[i])*(n-j)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=400010,INF=1e9;
typedef long long ll;
int stk[N];
ll A[N],B[N];
int main()
{
int T;
scanf("%d",&T);
for(int cases=1;cases<=T;cases++)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
A[i]=A[i-1]+x;
B[i]=B[i-1]+x*(n-i);
}
int top=0;
ll res=0;
A[n+1]=-INF;
stk[++top]=n+1;
for(int i=n;i>=0;i--)
{
while(A[stk[top]]>=A[i]) top--;
int j=stk[top];
stk[++top]=i;
res+=B[j-1]-B[i]-(A[j-1]-A[i])*(n-j);
}
printf("Case #%d: %lld\n",cases,res);
}
return 0;
}