题目描述
难度分:2700
输入T(≤10)表示T组数据。所有数据的n之和≤5×105。
每组数据输入n(1≤n≤5×105)和长为n的数组a(1≤a[i]≤106)。
输出a有多少个非空连续子数组b,满足max(b)是min(b)的倍数。
输入样例
6
1
1
2
2 4
2
2 3
4
2 4 7 14
7
16 5 18 7 7 12 14
6
16 14 2 6 16 2
输出样例
1
3
2
7
10
19
算法
单调栈+数论
首先,这个题使用完单调栈之后的分析和实现难度比较大。其次,这个题比较吃经验,不然估计不好时间复杂度根本不敢动手。
预处理
题目提到子数组要以某个值为最值,因此可以比较容易想到单调栈,先跑4次单调栈做预处理,得到pre_ge(为了避免重复统计,这里要取等号)、pre_lt、suf_gt、suf_lt这4个数组。其中pre_ge[i]、pre_lt、suf_gt、suf_lt分别表示a[i]左边大于等于自己、小于自己、右边大于自己、小于自己的最近位置。
有了这4个数组就可以枚举子数组的最大值a[i],当a[i]要作为子数组的最大值时,那这个子数组的左边界l就应该在区间(pre_ge[i],i]之内,右边界r就应该在区间[i,suf_gt[i])之内。此时就能得到一个粗略的范围,但是还需要受到子数组最小值为a[i]的约数的限制。
枚举因子
对于[1,106]内的数字,因子的最大个数只有不超过28个,所以可以对每个a[i]枚举因子,时间复杂度也才O(Dn)(D为最大因子数量),在本题的数据量下是可以过的。
当遍历到a[i]枚举到它的因子d时,找到i前面最近d的位置L,i后面最近d的位置R。令l=pre_ge[i]+1,r=suf_gt[i]−1,如果出现以下两种情况,肯定就不合法了(因为d必须在区间[l,r]内):
- 若L<l或suf_lt[L]<i,这时候L突破了a[i]为最大值时,左边界不能小于l的限制。且子数组内有比d更小的值,所以d不能成为最小值。
- 若R>r或pre_lt[R]>i,这时候R突破了a[i]为最大值时,右边界不能大于r的限制。且子数组内有比d更小的值,所以d不能成为最小值。
合法的情况有如下三种:
- L和R都是合法的,a[i]和d分别为最大值和最小值的子数组有(L−max(l,pre_lt[L]+1)+1)×(min(r,suf_lt[R]−1)−R+1)个。
- L是合法的,只取左边的d,a[i]和d分别为最大值和最小值的子数组有(L−max(l,pre_lt[L]+1)+1)×(min(r,R−1,suf_lt[L]−1)−i+1)个。
- R是合法的,只取右边的d,a[i]和d分别为最大值和最小值的子数组有(L−max(l,L+1,pre_lt[R]+1)+1)×(min(r,suf_lt[R]−1)−R+1)个。
a[i]对答案的贡献就是以上三种情况之和,最终答案就是所有a[i]的贡献。
复杂度分析
时间复杂度
分成以下几个部分讨论:
- 遍历a数组统计答案时的时间复杂度为O(Dn)。
- 单调栈预处理出4个前后缀的信息数组pre_ge、pre_lt、suf_lt、suf_gt,每个的时间复杂度都是O(n),整体上还是O(n)的。
- 而预处理每个数的因子时需要遍历i∈[1,106],对于每个i,在[1,A]中它的倍数有[Ai]个(本题中A=106,其中[.]表示对.向下取整),因此i属于[Ai]个元素的因子。一共要遍历ΣAi=1([A1]+[A2]+…+[AA])=ΣAi=1A(1+12+…+1A)次,根据调和级数的敛散性,这个时间复杂度为O(AlnA)。
综上,算法整体的时间复杂度为O(AlnA+Dn)。
空间复杂度
4个前后缀信息数组的空间是O(n)的;要存储每个数值的约数需要消耗的空间为O(AlnA);还需要存储每个数值的索引列表vecL和vecR,由于最多O(n)个数值,一共存O(n)个索引,所以空间消耗为O(n)。
综上,算法整体的额外空间复杂度为O(n+AlnA)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <vector>
#pragma GCC optimize(2)
using namespace std;
typedef long long LL;
const int N = 500010, M = 1000001;
vector<int> vecL[M], vecR[M], graph[M];
int T, n, a[N], pre_ge[N], suf_gt[N], pre_lt[N], suf_lt[N];
bool flag;
void add(int u, int v){
graph[u].push_back(v);
}
void clear_env() {
for(int i = 1; i <= n; i++) {
vecL[a[i]].clear();
vecR[a[i]].clear();
pre_lt[i] = suf_lt[i] = pre_ge[i] = suf_gt[i] = 0;
}
}
int main() {
if(!flag) {
for(int i = 1; i <= M - 1; i++) {
for(int j = i*2; j <= M - 1; j += i) {
add(j, i);
}
}
flag = true;
}
scanf("%d", &T);
while(T--) {
scanf("%d", &n);
LL ans = 0, last = 0;
// 读入数据的时候就把最大值等于最小值的段统计掉
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if(a[i] != a[i - 1]) last = i;
ans += i - last + 1;
}
// 单调栈预处理
stack<int> stk;
// 前面比自己小的数的最近位置
for(int i = 1; i <= n; i++) {
while(stk.size() && a[stk.top()] >= a[i]) stk.pop();
pre_lt[i] = stk.size()? stk.top(): 0;
stk.push(i);
}
while(stk.size()) stk.pop();
// 后面比自己小的数的最近位置
for(int i = n; i >= 1; i--) {
while(stk.size() && a[stk.top()] >= a[i]) stk.pop();
suf_lt[i] = stk.size()? stk.top(): n + 1;
stk.push(i);
}
while(stk.size()) stk.pop();
// 前面小于等于自己的最近位置
for(int i = 1; i <= n; i++) {
while(stk.size() && a[stk.top()] < a[i]) stk.pop();
pre_ge[i] = stk.size()? stk.top(): 0;
stk.push(i);
}
while(stk.size()) stk.pop();
// 后面大于自己的最近位置
for(int i = n; i >= 1; i--) {
while(stk.size() && a[stk.top()] <= a[i]) stk.pop();
suf_gt[i] = stk.size()? stk.top(): n + 1;
stk.push(i);
}
// 统计
for(int i = n; i >= 1; i--) {
vecR[a[i]].push_back(i);
}
for(int i = 1; i <= n; i++) {
vecR[a[i]].pop_back();
int l = pre_ge[i] + 1, r = suf_gt[i] - 1;
for(int d: graph[a[i]]) {
int L = vecL[d].empty()? l - 1: vecL[d].back();
if(suf_lt[L] < i) L = l - 1;
int R = vecR[d].empty()? r + 1: vecR[d].back();
if(pre_lt[R] > i) R = r + 1;
if(L < l && R > r) continue;
if(L >= l && R <= r) ans += 1LL*(L - max(l, pre_lt[L] + 1) + 1) * (min(r, suf_lt[R] - 1) - R + 1);
if(L >= l) ans += 1LL*(L - max(l, pre_lt[L] + 1) + 1)*(min(min(r, R - 1), suf_lt[L] - 1) - i + 1);
if(R <= r) ans += 1LL*(i - max(max(l, L + 1), pre_lt[R] + 1) + 1) * (min(r, suf_lt[R] - 1) - R + 1);
}
vecL[a[i]].push_back(i);
}
printf("%lld\n", ans);
clear_env();
}
return 0;
}