题意:给定一个n个数的序列,定义一个函数f(l, r),表示在[l, r]这个区间内,满足不是区间内任何其他数的倍数的数的个数。比如一个区间内的数为2,3,4,那么函数值为2。因为2和3都不是其他任何数的倍数,但4是2的倍数。现在要求所有的区间的函数值的和。
思路:相对于计算每个区间内有多少个这样的数,我们不如来看每个数被计算了多少次。每个数左面第一个不是他约数的数到右面第一个不是它约数的数,这样一个区间里的所有子区间 这个数都会被计算一次。设这个数的位置是pos,左右的那个位置分别是L,R,那么这个数被计算了(pos-L)*(R-pos)次。考虑到每个数最大也只有10000,不由得想到去预处理。首先预处理出1到10000每个数的约数,然后,对于数列里某个数,枚举它的所有约数(最多64个),用vector记录每个约数出现在哪些位置上(即下标),再二分找到左边最近的和右边最近的位置,更新离它最近的约数的位置即可。这样只有log(n)复杂度。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <stack>
#include <vector>
#include <map>
#include <set>
using namespace std;
const int MAX = 1e5 + 5;
const int MOD = 1e9 + 7;
vector <int> yueshu[MAX], pos[MAX];
int n, a[MAX];
void initial() //预处理约数
{
for(int i = 0; i < MAX; i++)
yueshu[i].clear();
for(int i = 1; i <= 10000; i++)
{
for(int j = 1; j*j <= i; j++)
{
if(i%j == 0)
{
yueshu[i].push_back(j);
if(j*j != i)
yueshu[i].push_back(i/j);
}
}
}
}
void input()
{
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
}
void solve()
{
for(int i = 0; i < MAX; i++)
pos[i].clear();
for(int i = 1; i <= n; i++) //记录每个数出现的所有下标
pos[a[i]].push_back(i);
long long ans = 0;
for(int i = 1; i <= n; i++)
{
int maxl = 0, minr = n + 1;
for(int j = 0; j < yueshu[a[i]].size(); j++) //枚举约数
{
int temp = yueshu[a[i]][j];
if(pos[temp].size() == 0)
continue;
if(pos[temp][0] > i)
{
minr = min(minr, pos[temp][0]);
continue;
}
if(pos[temp][pos[temp].size() - 1] < i)
{
maxl = max(maxl, pos[temp][pos[temp].size() - 1]);
continue;
}
int l = 0, r = pos[temp].size() - 1, mid;
while(l < r - 1) //二分找离a[i]左边和右边最近的当前约数
{
mid = (l + r)/2;
if(pos[temp][mid] < i)
l = mid;
else
r = mid;
}
if(r + 1 < pos[temp].size()) //更新两边最近约数的位置
minr = min(minr, pos[temp][r + 1]);
if(pos[temp][r] > i)
minr = min(minr, pos[temp][r]);
if(pos[temp][l] < i)
maxl = max(maxl, pos[temp][l]);
}
ans = (ans + (long long)(i - maxl)*(minr - i))%MOD;
}
printf("%I64d\n", ans);
}
int main()
{
initial();
while(scanf("%d", &n) != EOF)
{
input();
solve();
}
return 0;
}