legendre 勒让德定理 例:阶乘除法

一.Legendre 勒让德定理

勒让德定理:在n!中,质数p的最高次数(指数)为\sum_{k=1}^{p^{k}\leq n}[ \frac{n}{p^{k}}]

一定记住这个结论!!!

还有,代码实现可以直接爆搜。。。

二.例题:阶乘除法

1.题目

题目描述

 

输入

第一行三个整数,n,m和T。

第二行n个数,第i个数表示ai。

第三行m个数,第i个数表示bi。

输出

输出一个数,答案对T取余数的结果。

样例输入

3 2 998244353

2 2 6

3 3

样例输出

80

提示

 

2.思路

相信大家都会了勒让德定理吧。那么这道题,首先就根据勒让德定理,用筛法筛一个素数表(埃筛和欧筛都可以)。也就有了一种爆搜解法:

\sum_{i = 1}^{i<=n}\sum_{j=1}^{j<=pn}\sum_k[\frac{a[i]}{prime[j]^{k}}](i表示需要进行阶乘运算的数的下标,如:样例的a[1] = 2;pn表示素数的总个数,prime[j]表示素数,如:prime[1] = 2)

但是,众所周知,这样是一定会超时的,因为第一层循环耗时太久。


现在,我们开始优化:为了去掉第一层循环,我们可以使用桶。怎么使用呢?我们要用桶存下每一个需要阶乘的数,到时候就可以将所有的数一同处理。我们把这个桶命名为c[i],这个桶的意义是:值为i的数的个数。就比如说样例:c[2] = 2, c[6] = 1。

再者,因为一个数n,依次除以几个连续的数再下取整的结果是有分段性的,就比如:5来除以2,3,4,5,6,7,8,9,10,11再下取整,那么商分别为:0,0,0,1,1,1,1,1,2,2。那么每个段有多长呢?一定有这么长:对于下去整商k,当除数是j时,(k*j)((k+1)*j-1 )除以j后下去整商等于k。我们可以将这个分段性和桶联合起来使用。

既然这是一段一段的,我们不如想出一个办法可以一次性算出某一段中有多少个数。不难想到,前缀和是最理想的,所以把桶升级一下,存前缀和。

一共三层循环:

第一层循环:从小到大枚举素数prime[i]

for (int i = 1; i <= pn; i ++)

第二层循环:从小到大枚举素数的指数,并算出prime[j]^{k}

for (LL j = prime[i]; j <= MAX; j *= prime[i])
//j直接算出prime[i]的k次方

第三层循环:枚举下取整之后的商,prime[j]^{k}作为除数,算出这一段中有多少个数。

因为如果边进行除法运算,边进行模运算会出错,所以我们选择存下每个素数的指数,最后除完之后,再一一进行乘方运算并进行模运算。

于是第三层循环还要做:算出这一段中有多少个数,再乘以下取整之后的商,再将这些结果累加起来,就是prime[i]的最高次数。

for (LL k = 1; k * j <= MAX; k ++){
     LL l = k * j, r = min ((k + 1) * j - 1, MAX);
     w[i] += (c[r] - c[l - 1]) * k;
}//w[i]指prime[i]的最高次数

OK,代码主要部分就完了,最后就是运用快速幂累乘。

3.样例代码

#include 
#include 
#include 
using namespace std;
#define M 100005
#define LL long long
LL ans = 1, n, m, T, a, c[M], pn, prime[M], w[M], MAX;
bool vis[M];
void seive (int x){
    for (int i = 2; i <= x; i ++){
        if (! vis[i]){
            prime[++ pn] = i;
            vis[i] = 1;
            for (int j = i * 2; j <= x; j += i)
                vis[j] = 1;
        }
    }
}
LL qkpow (LL x, LL y){
    LL sum = 1;
    while (y > 0){
        if (y % 2 == 1)
            sum = sum * x % T;
        x = x * x % T;
        y /= 2;
    }
    return sum;
}
int main (){
    scanf ("%lld %lld %lld", &n, &m, &T);
    seive (100000);//筛法,范围不超十万
    for (int i = 1; i <= n; i ++){
        scanf ("%lld", &a);
        c[a] ++;
        MAX = max (MAX, a);
    }
    for (int i = 1; i <= MAX; i ++)//前缀和处理
        c[i] += c[i - 1];
    for (int i = 1; i <= pn; i ++){
        for (LL j = prime[i]; j <= MAX; j *= prime[i]){
            for (LL k = 1; k * j <= MAX; k ++){
                LL l = k * j, r = min ((k + 1) * j - 1, MAX);
                w[i] += (c[r] - c[l - 1]) * k;//先分解被除数,所以最高指数要加起来
            }
        }
    }
    MAX = 0;
    memset (c, 0, sizeof(c));//注意清零
    for (int i = 1; i <= m; i ++){
        scanf ("%lld", &a);
        MAX = max (MAX, a);
        c[a] ++;
    }
    for (int i = 1; i <= MAX; i ++)
        c[i] += c[i - 1];
    for (int i = 1; i <= pn; i ++){
        for (LL j = prime[i]; j <= MAX; j *= prime[i]){
            for (LL k = 1; k * j <= MAX; k ++){
                LL l = k * j, r = min ((k + 1) * j - 1, MAX);
                w[i] -= (c[r] - c[l - 1]) * k;//这次分解除数,所以最高指数要相减
            }
        }
    }
    for (int i = 1; i <= pn; i ++){
        ans = (ans * qkpow (prime[i], w[i])) % T;//最后累乘
    }
    printf ("%lld\n", ans);
    return 0;
}

Legendre、Legendre、Legendre!

你可能感兴趣的:(数论,legendre)