J - Prime Game ( 筛素因子 + 思维算贡献 )
1.题意:给出n个数字的序列,求:
其中:fac(i,j)是指区间[i,j]内数字乘积的素数因子的个数(重复算一个)
(有一个小定义是,两个数乘积的素因子就是原来数的素因子,比如12 和 7 素因子分别为{2,3}和{7}, 那么12*7=84的素因子就是{2,3,7} 。)
2.思路:
因为1<=n<=1e6 ; 1<=ai<=1e6
所以直接算乘积是不行的,a*b*c*d*...的素数因子和单独计算每一个的素数因子取交集是一样的。
因为暴力是肯定不行的,所以我们需要计算每一个素数因子在每个区间里面的贡献。
因为枚举每个数字复杂度为O(n),唯一分解定理数出素因子复杂度为O(logn),所以nlogn的复杂度是可以接受的。
对于某个位置 i 处的数字,假设其某个素因子在位置 i 之前最近出现过的位置为 j
则位置 i 处的数字的素因子对整个结果的贡献为:
首先从[i,n],该素因子被贡献(n - i + 1)次
在j+1处开始一直到i,[j+1,n] , [j+2,n] , ...[i,n] 都是出现i处的该素因子,所以共贡献了:
(i - j)*(n - i + 1) 次该素因子在 i 处时的贡献。
具体实现中一个pre数组用的非常巧妙。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+10;
int primes[maxn],cnt;
bool isp[maxn];
int pre[maxn]; // pre[7]=12, 表示素因子7最近一次出现在12号位置
void get_prime() // 得到素数表
{
int i;
cnt = 0;
for ( i=0; i<=maxn; i++ ) isp[i]=1;
isp[0] = isp[1] = 0;
for ( i=2; i*i<=maxn; i++ ) {
if ( isp[i]==1 ) {
isp[i] = 0;
primes[cnt++] = i;
for ( int j=i*i; j<=maxn; j+=i ) {
isp[j] = 0;
}
}
}
for ( i; i<=maxn; i++ ) {
if ( isp[i]==1 ) primes[cnt++] = i;
}
}
int main()
{
get_prime();
memset(pre,0,sizeof(pre)); // 初始化为0, 方便公式运行
ll n,sum=0,x;
cin >> n;
for ( int i=1; i<=n; i++ ) {
scanf("%lld",&x);
for ( int j=0; primes[j]<=sqrt(x)&&j<cnt; j++ ) { // 得到素因子
if ( x%primes[j]==0 ) {
sum += (i-pre[primes[j]])*(n-i+1); // 计算贡献值公式
pre[primes[j]] = i; // 更新pre的值为当前i
while ( x%primes[j]==0 ) {
x /= primes[j];
}
}
}
if ( x>1 ) { // 如果x>1, 表示还存在一个素因子x
sum += (i-pre[x])*(n-i+1);
pre[x] = i;
}
}
cout << sum << endl;
return 0;
}