洛谷 2155[SDOI2008]沙拉公主的困惑 题解(数论)

博客围绕给定n、m(n>=m),求[1,n!]中与m!互质的数的数量展开。先简述题意和数据输入输出,接着分析思路,利用01序列特性,结合欧拉函数公式,通过预处理质数、计算前缀积和逆元来求解,最后提及代码相关。

原题链接:
洛谷:点我QωQ
bzoj:点我QωQ
(常数过不去。。。用另一种写法过的)

题意简述

给定 n , m n,m n,m,保证 n > = m n>=m n>=m,求 [ 1 , n ! ] [1,n!] [1,n!]中与 m ! m! m!互质的有多少。

数据

输入

一行两个正整数 n , m ( n , m &lt; = 1 e 7 ) n,m(n,m&lt;=1e7) n,m(n,m<=1e7)

输出

如题意简述

思路

我们会发现,对于一个数 p p p,用一个 01 01 01序列表示一个序列和 p p p互质的情况, 0 0 0表示不互质, 1 1 1表示互质。那么, [ 1 , p ] [1,p] [1,p]的这个 01 01 01序列和 [ p + 1 , 2 p ] [p+1,2p] [p+1,2p], [ 2 p + 1 , 3 p ] , ⋯ [ ( n − 1 ) p + 1 , n p ] [2p+1,3p],\cdots [(n-1)p+1,np] [2p+1,3p],[(n1)p+1,np]是一样的。

不信?我们试试。如 p = 9 p=9 p=9。那么, [ 1 , p ] [1,p] [1,p]的这个 01 01 01序列应该等于
1   1   0   1   1   0   1   1   0 1\ 1\ 0\ 1\ 1\ 0\ 1\ 1\ 0 1 1 0 1 1 0 1 1 0
会发现, [ p + 1 , 2 p ] [p+1,2p] [p+1,2p]的这个 01 01 01序列也等于:
1   1   0   1   1   0   1   1   0 1\ 1\ 0\ 1\ 1\ 0\ 1\ 1\ 0 1 1 0 1 1 0 1 1 0

所以,设 [ 1 , p ] [1,p] [1,p]中和 p p p互质的有 k k k个,那么 [ 1 , n p ] [1,np] [1,np]中和 p p p互质的就有 n k nk nk个。回到这个题中,我们只要求出 [ 1 , m ! ] [1,m!] [1,m!]中多少个和 m ! m! m!互质,然后用它 ∗ n ! m ! *\frac{n!}{m!} m!n!即珂。但是,如何求 [ 1 , m ! ] [1,m!] [1,m!]中多少个和 m ! m! m!互质(即 ϕ ( m ! ) \phi(m!) ϕ(m!))呢?

我们知道一个式子: ϕ ( x ) = x ∗ ∏ i = 1 k p i − 1 p i \phi(x)=x*\prod\limits_{i=1}^{k}\frac{p_i-1}{p_i} ϕ(x)=xi=1kpipi1,其中 p 1 , p 2 . . . p k p_1,p_2...p_k p1,p2...pk x x x的所有质因数。即 x x x ϕ \phi ϕ值为: x ∗ x 所 有 质 因 数 − 1 x 所 有 质 因 数 x*\frac{x所有质因数-1}{x所有质因数} xxx1。而 m ! m! m!的所有质因数就是 1   m 1~m 1 m中的所有质数。也就是说,我们的答案就是
n ! m ! ∗ ∏ i = 1 k p k − 1 p k \frac{n!}{m!}*\prod\limits_{i=1}^{k}\frac{p_k-1}{p_k} m!n!i=1kpkpk1。(其中 p k p_k pk m m m以内最大的质数)

所以,我们预处理好所有的质数,然后设 p r e [ i ] pre[i] pre[i]为前 i i i个质数的 p − 1 p \frac{p-1}{p} pp1值之积(即前缀积)。每次询问的时候, u p p e r _ b o u n d upper\_bound upper_bound一下 m m m的位置,乘一下即珂。当然我们也要求出阶乘(也是前缀积的思想)。会发现,那个 p r e pre pre数组在算的时候要涉及到除法,两个阶乘也要除,那没问题,跑一下逆元就珂以了。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 10000005
    #define int long long
    int t,r;
    int n,m;

    int primes[N];bool notp[N];//质数
    int inv[N];//逆元
    int fact[N];//阶乘
    int pre[N];//前缀积
    int qpow(int a,int b,int m)//快速幂
    {
        int r=1;
        while(b)
        {
            if (b&1) r=r*a%m;
            a=a*a%m,b>>=1;
        }
        return r;
    }
    void Init()//O(n)=O(1e7)
    {
        int n=10000000;
        int& cnt=primes[0];
        notp[1]=1;
        for(int i=2;i<=n;++i)
        {
            if (!notp[i])
            {
                primes[++cnt]=i;
            }
            for(int j=1;j<=cnt and i*primes[j]<=n;++j)
            {
                int u=primes[j];
                notp[i*u]=1;
                if (i%u==0) break;
            }
        }//处理质数

        for(int i=1;i<=primes[0];++i)
        {
            inv[i]=qpow(primes[i],r-2,r);
        }//处理质数的逆元

        fact[0]=1;
        for(int i=1;i<=n;++i)
        {
            fact[i]=fact[i-1]*i%r;
        }//处理阶乘

        pre[0]=1;
        for(int i=1;i<=primes[0];++i)
        {
            int u=primes[i];
            pre[i]=pre[i-1]*(u-1)%r*inv[i]%r;
        }//处理pre
    }

    void Solve()
    {
        int ans=fact[n];
        int pos=upper_bound(primes+1,primes+primes[0]+1,m)-primes-1;
        //upper_bound之后-1是最后一个<=某个数的位置
        //lower_bound是第一个<=某个数的位置,所以不能用lower_bound
        ans*=pre[pos];ans%=r;
        printf("%lld\n",ans);
    }
    void Main()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        scanf("%lld%lld",&t,&r);
        Init();
        while(t--)
        {
            scanf("%lld%lld",&n,&m);
            Solve();
        }
    }
    #undef N //10000005
    #undef int //long long
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

回到总题解界面

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值