[数论] 阶乘除法

文章目录

  • 题目描述
  • 输入
  • 输出
  • 样例输入
  • 样例输出
  • 解题思路
  • 参考代码


题目描述

输入

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

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

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

输出

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

样例输入

3 2 998244353
2 2 6
3 3

样例输出

80


解题思路

很容易发现这是一道需要用到legendre定理的题目
我们就直接用legendre跑一遍,很明显会超时,所以我们就要考虑一下优化
显然我们是要先用legendre分解质因数的,所以我们可以先用欧拉筛法求出数据范围内的所有质数,免去了每次判断质数所用去的时间
我们用一个数组 p p p来保存每一个数所出现的次数,然后我们探究一个东西:
我们知道勒让德定理是这样的: F ( n ) = ∑ k = 1 ⌊ n a k ⌋ F(n)=\sum_{k=1}\lfloor \frac{n}{a^k} \rfloor F(n)=k=1akn
其中 a a a是一个质数,现在我们假设 a k a^k ak为2,那么可以得到 ⌊ 2 2 ⌋ = ⌊ 3 2 ⌋ \lfloor \frac{2} {2} \rfloor=\lfloor \frac{3}{2} \rfloor 22=23
假设 a k a^k ak为5,那么可以得到 ⌊ 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 55=56=57=58=59
由此可以发现,当 a k a^k ak为一个定值时,如果要使 [ l , r ] \lbrack l,r \rbrack [l,r]这个区间内里的所有数除以 a k a^k ak下取整都为 x x x(在上面的两个例子中都为1),如何确定 l l l r r r的值呢?
L L L很明显,肯定是 a k ∗ x a^k*x akx,关于 r r r的取值,我们先看上面的例子:
第一个例子中 r r r为3,而 2 ∗ 2 − 1 = 3 2*2-1=3 221=3,因为 x x x是等于1的,所以就是 2 ∗ ( x + 1 ) − 1 = 3 2*(x+1)-1=3 2(x+1)1=3,第二个例子同理
所以 r = a k ∗ ( x + 1 ) − 1 r=a^k*(x+1)-1 r=ak(x+1)1
也可以这样解释: ⌊ a k ∗ x a k ⌋ = x , ⌊ a k ∗ ( x + 1 ) a k ⌋ = x + 1 , ∴ ⌊ a k ∗ ( x + 1 ) − 1 a k ⌋ = x \lfloor \frac{a^k*x}{a^k} \rfloor=x,\lfloor \frac{a^k*(x+1)}{a^k} \rfloor=x+1,\therefore\lfloor \frac{a^k*(x+1)-1}{a^k} \rfloor=x akakx=xakak(x+1)=x+1akak(x+1)1=x
总结一下就是:

[ a k ∗ x , a k ∗ ( x + 1 ) − 1 ] \lbrack a^k*x,a^k*(x+1)-1 \rbrack [akx,ak(x+1)1]这个区间内的所有数 i i i满足 ⌊ i a k ⌋ = x \lfloor \frac{i}{a^k} \rfloor=x aki=x

区间和用前缀和来做就可以了,我们就用一个 w w w数组,其中 w [ i ] w[i] w[i]表示第 i i i个质数在 n ! n! n!中的指数,所以 w [ i ] = ( p [ r ] − p [ l − 1 ] ) ∗ x w[i]=(p[r]-p[l-1])*x w[i]=(p[r]p[l1])x
因为 p [ r ] − p [ l − 1 ] p[r]-p[l-1] p[r]p[l1]只是 ⌊ l , r ⌋ \lfloor l,r \rfloor l,r这个区间中除以 a k a^k ak下取整为 x x x的所有数的总量,所以每一个数都会贡献一个 x x x加到第 i i i个数的指数里面,所以要乘以一个 x x x


参考代码

#include 
#include 
#define reg register
 
template <class T>
inline T read() {
    T x = 0; T f = 1; char s = getchar();
    while(s < '0' || s > '9') {if(s == '-') f = -1; s = getchar();}
    while(s >= '0' && s <= '9') {x = (x << 3) + (x << 1) + s - 48; s = getchar();}
    return x * f;
}
 
template <typename T>
inline void wri(T x) {
    if(x < 0) {x = -x; putchar('-');}
    if(x / 10) wri(x / 10);
    putchar(x % 10 + 48);
}
 
template <typename T>
inline void write(T x, char s) {
    wri(x);
    putchar(s);
}
 
template <typename T>
inline T Max(T x, T y) {return x > y ? x : y;}
 
template <typename T>
inline T Min(T x, T y) {return x < y ? x : y;}
 
#define MAXN 100005
#define LL long long
 
LL T, ans = 1;
LL p[MAXN], w[MAXN];
int n, m, len, maxn;
int prim[MAXN];
bool vis[MAXN];
 
inline void Memset() {
    for(reg int i = 0;i <= maxn;i ++)
        p[i] = 0;
    maxn = 0;
}
 
inline void sieve(int x) {	//欧拉筛法求出MAXN以内的所有质数
    for(reg int i = 2;i <= x;i ++) {
        if(! vis[i])
            prim[++ len] = i;
        for(reg int j = 1;j <= len && i * prim[j] <= x;j ++) {
            vis[i * prim[j]] = 1;
            if(i % prim[j] == 0)
                break;
        }
    }
}
 
inline void legendre(int x, int f) {	//legendre定理
    for(reg int i = 1;i <= x;i ++)	//前缀和
        p[i] += p[i - 1];
    for(reg int i = 1;i <= len && prim[i] <= x;i ++)	//枚举质数
        for(reg LL j = prim[i];j <= x;j *= prim[i])		//枚举上述推导中的a^k
            for(reg LL k = 1;k * j <= x;k ++) {		//枚举上述推导中的x
                LL l = k * j, r = Min(1ll * maxn, 1ll * ((k + 1) * j - 1));
                w[i] += (p[r] - p[l - 1]) * k * f;
            }
}
 
inline LL qkpow(LL x, LL y) {	//快速幂
    LL tot = 1;
    while(y > 0) {
        if(y & 1)
            tot = tot * x % T;
        x = x * x % T;
        y >>= 1;
    }
    return tot;
}
 
int main() {
    n = read<int>(), m = read<int>(), T = read<LL>();
    sieve(MAXN);
    for(reg int i = 1;i <= n;i ++) {
        int x = read<int>();
        p[x] ++;
        maxn = Max(maxn, x);
    }
    legendre(maxn, 1);
    Memset();
    for(reg int i = 1;i <= m;i ++) {
        int x = read<int>();
        p[x] ++;
        maxn = Max(maxn, x);
    }
    legendre(maxn, -1);
    for(reg int i = 1;i <= len;i ++)
        ans = ans * qkpow(prim[i], w[i]) % T;
    write(ans, '\n');
    return 0;
}

你可能感兴趣的:(数论,OI路上的题解)