Perfect Subarray
题目链接
题目简介
如果一个数组中所有数的和是平方数,那么这个数组称为完美数组。求一个数组的子数组中有多少个是完美数组。
解题思路
- 前缀和的思想
- 遍历这个数组 a a a,在遍历到第 x x x个元素时, p r e f i x prefix prefix(前缀和)记录 ∑ i = 0 x a [ i ] \sum_{i=0}^{x}a[i] ∑i=0xa[i],我们现在想求以第 x x x个元素为最后一个元素的子数组是完美数组的数量
- 如果我们记录下每一个前缀和出现的次数(用 s s s数组, s [ i ] s[i] s[i]表示前缀和为 i i i的子数组出现的数量)
- 那么以第 x x x个元素为最后一个元素的子数组是完美数组的数量就是 s [ p r e f i x − j ∗ j ] s[prefix-j*j] s[prefix−j∗j]( j ≥ 0 , j ∗ j 是 我 们 要 遍 历 的 平 方 数 j \geq 0,j*j是我们要遍历的平方数 j≥0,j∗j是我们要遍历的平方数 ),为什么是这个式子,原因就是用前缀和为 p r e f i x prefix prefix的子数组减去前缀和为 p r e f i x − j ∗ j prefix-j*j prefix−j∗j的子数组得到的子数组的前缀和为 j ∗ j j*j j∗j,即平方数。也就是说有多少个前缀和为 p r e f i x − j ∗ j prefix-j*j prefix−j∗j的子数组就有多少个完美子数组。(他们都以第 x x x个元素为最后一个元素)
- 这时,我们在考虑另外一些问题,前缀和有可能是负数,这样就不能用数组索引了,为了解决这个问题,我们将前缀和加一个偏移量,这个偏移量就等于这个数组中所有负数的和的相反数(s1)。
- 还有就是 s [ 0 ] s[0] s[0]的初值要设置成1,因为如果 p r e f i x − j ∗ j prefix-j*j prefix−j∗j的值为0就代表它本身就是完美数组,我们也要考虑进去这种情况
- 我们要遍历的平方数的大小一定会小于这个数组中所有正数之和(s2)
代码
代码中的符号基本与解题思路中的符号 一 一 对应
#include<iostream>
#include<cstring>
#define MAXN 100010
using namespace std;
int T,N,a[MAXN],s[100*MAXN],s1,s2;
long long ans;
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin >> T;
for(int g=1;g<=T;g++){
ans = 0;s1=0;s2=0;
cin >> N;
for(int i=1;i<=N;i++){
cin >> a[i];
if(a[i]<0)s1 -= a[i];
else s2 += a[i];
}
int prefix=0;
s[s1]++;
for(int i=1;i<=N;i++){
prefix += a[i];
for(int j=0;j*j<=s2;j++){
if(s1+prefix-j*j>=0)
ans += s[s1+prefix-j*j];
}
s[s1+prefix]++;
}
memset(s,0,sizeof(int)*(s1+s2+1));
cout << "Case #" << g << ": " << ans << "\n";
}
}