阶乘除法(非NOJ——1634,Legendre定理)

题目描述

Alt

输入

第一行三个整数,n,m和T。
第二行n个数,第i个数表示ai。
第三行m个数,第i个数表示bi。

输出

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

样例输入

3 2 998244353
2 2 6
3 3

样例输出

80

提示

在这里插入图片描述

思路

不知道Legendre定理的点这里
不会实现的点这里
用了Legendre,这道题的思路就很明显了,用它把分子和分母分解一下, 再除一下,但除法无法取余,所以我们用b[i]来存i的次数,分子有的就加,分母有的就减(同一数项可以相抵消),然后再用qkp取余乘一下就行了。当然了,先用欧拉筛法把100000以内的素数筛出来。

代码大概就长这个样子:

#include <cstdio>
#include <cctype>
#include <iostream>
using namespace std;
 
int n, m, t, cnt, u, maxn, ans=1;
 
int a[100005], prime[66666];
 
bool vis[100005];
 
void Rd(int &x){
    x = 0;
    int f = 1; char c = getchar();
    while( !isdigit(c) ) { if(c == '-') f = -1; c = getchar(); }
    while( isdigit(c) ) { x=(x<<1)+(x<<3)+(c^48); c = getchar(); }
    x *= f;
}
 
void sieve(int x){
    for(int i = 2; i <= x; i ++){
        if( !vis[i] )
            prime[++cnt] = i;
        for(int j = 1; j <= cnt && prime[j]*i <= x; j ++){
            vis[prime[j]*i] = 1;
            if( i % prime[j] == 0 )
                break;
        }
    }
}
 
int qkp(int x, int y){
    int sum = 1;
    while( y ){
        if( y&1 )
            sum = sum%t*x%t;
        y >>= 1;
        x = x%t*x%t;
    }
    return sum;
}
 
int main(){
    Rd(n), Rd(m), Rd(t);
    sieve(100000);
    for(int i = 1; i <= n; i ++){
        Rd(u);
        for(int j = 1; j <= cnt && prime[j] <= u; j ++){
            int p = 1;
            while( p*prime[j] <= u ){
                p *= prime[j];
                a[prime[j]] += u/p;
            }
            maxn = max(maxn, prime[j]);
        }
    }
    for(int i = 1; i <= m; i ++){
        Rd(u);
        for(int j = 1; j <= cnt && prime[j] <= u; j ++){
            int p = 1;
            while( p*prime[j] <= u ){
                p *= prime[j];
                a[prime[j]] -= u/p;
            }
            maxn = max(maxn, prime[j]);
        }
    }
    for(int i = 2; i <= maxn; i ++)
        ans = ans*qkp(i, a[i])%t;
    printf("%d\n", ans);
    return 0;
}

极限数据时间复杂度呢大概就是O(1918400000),很明显超时,接下来我们考虑优化

优化

因为我们知道legendre的核心大概长这样: n p k \lfloor \frac{n}{p^k} \rfloor
但我们会发现它存在阶段性,例如 5 5 = 6 5 = 7 5 = 8 5 = 9 5 \lfloor \frac{5}{5} \rfloor=\lfloor \frac{6}{5} \rfloor=\lfloor \frac{7}{5} \rfloor=\lfloor \frac{8}{5} \rfloor=\lfloor \frac{9}{5} \rfloor
也就是说在某个范围内的 n p k \lfloor \frac{n}{p^k} \rfloor 是一定的,我们只要知到这个区间的中有多少个数,那么这个区间就有多少个 n p k \lfloor \frac{n}{p^k} \rfloor ,就不用再认认真真的搞每个数,我们求区间和就用前缀和,此时我们就用一个桶数组来存放数,再求一次前缀和就可以知道某个区间的数的个数了。
那我们如何确定区间的左右端点呢?
其实我们可以发现只要 n m \lfloor \frac{n}{m} \rfloor (m>n)那么这个式子的值其实为0。
所以,我们只要枚举 n p k \lfloor \frac{n}{p^k} \rfloor 的值i,然后乘一下 p k {p^k} i p k i*{p^k} 就是这个区间的左端点,由上面一条我们可以知道只要 i p k + m i*{p^k}+m m &lt; p k m&lt;{p^k} )那么它除以 p k {p^k} 的值就与 i p k i*{p^k} 除以 p k {p^k} 的值是相同的,所以只要 m m 取最大值 p k 1 {p^k}-1 就行了,化简一下右端点就可以变为 i + 1 p k 1 (i+1)*{p^k}-1 ,即左端点+ p k 1 {p^k}-1
n p k \lfloor \frac{n}{p^k} \rfloor 其实就是质因子 p p 的个数,我们再用一个数组存质因子 p p 的指数,处理分子的时候加,处理分母的时候减。最后再来一个qkp就行啦
希望读者提出宝贵意见,谢谢啦

参考代码

#include <cstdio>
#include <cctype>
#include <cstring>
#include <iostream>
using namespace std;
#define LL long long
#define M 100005

int n, m, t, cnt, u, maxn;

int a[M], prime[10000];
LL b[M];

bool vis[M];

void Rd(int &x){
    x = 0;
    int f = 1; char c = getchar();
    while( !isdigit(c) ) { if(c == '-') f = -1; c = getchar(); }
    while( isdigit(c) ) { x=(x<<1)+(x<<3)+(c^48); c = getchar(); }
    x *= f;
}

void sieve(int x){
    for(int i = 2; i <= x; i ++){
        if( !vis[i] )
            prime[++cnt] = i;
        for(int j = 1; j <= cnt && prime[j]*i <= x; j ++){
            vis[prime[j]*i] = 1;
            if( i % prime[j] == 0 )
                break;
        }
    }
}

LL qkp(LL x, LL y){
    LL sum = 1;
    while( y ){
        if( y&1 )
            sum = sum%t*x%t;
        y >>= 1;
        x = x%t*x%t;
    }
    return sum;
}

int main(){
    Rd(n), Rd(m), Rd(t);
    sieve(100000);
    for(int i = 1; i <= n; i ++){
        Rd(u);
        a[u]++;
        maxn = max(u, maxn);
    }
    for(int i = 1; i <= maxn; i ++)
        a[i] += a[i-1];
    for(int i=1;i<=cnt&&prime[i]<=maxn;i++){
        for(LL j=prime[i];j<=maxn;j*=prime[i]){
            for(LL k=1;k*j<=maxn;k++){
                LL l = j*k;
                LL r = 1ll*min(maxn*1ll, 1ll*(l+j-1));
                b[i] += (a[r]-a[l-1])*k;
            }
        }
    }
    memset(a, 0, sizeof(a));
    for(int i = 1; i <= m; i ++){
        Rd(u);
        a[u]++;
        maxn = max(maxn, u);
    }
    for(int i = 1; i <= maxn; i ++)
        a[i] += a[i-1];
    for(int i=1;i<=cnt&&prime[i]<=maxn;i++){
        for(LL j=prime[i];j<=maxn;j*=prime[i]){
            for(LL k=1;k*j<=maxn;k++){
                LL l = 1ll*j*k;
                LL r = 1ll*min(maxn*1ll, 1ll*(l+j-1));
                b[i] -= (a[r]-a[l-1])*k;
            }
        }
    }
    LL ans = 1;
    for(int i = 1; i <= cnt; i ++)
        ans = ans*qkp(prime[i], b[i])%t;
    printf("%lld\n", ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zyz_bz/article/details/87971826