J - Prime Game ( 筛素因子 + 思维算贡献 )

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;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值