bzoj2186 [Sdoi2008]沙拉公主的困惑

本文介绍了一种计算特定条件下真钞数量的方法。利用欧拉函数的性质,通过线性筛法预处理质数,并结合逆元的快速计算技巧,实现了高效求解。适用于大范围数值的真钞数量估算。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description


  大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。

R<=10^9+10
T<=10000
对于100%的数据,1 < = N , M < = 10000000 m<=n

Solution


一脸眼熟的题意
相当于求

φ(M!)N!M!φ(M!)∗N!M!

我们知道欧拉函数是可以拆一下的,设M!=pk11pk22...pknnM!=p1k1p2k2...pnkn
=N!Pi1Pi原式=N!∏Pi−1Pi

显然M!的质因数都不大于M,线性筛一下就好了,打一个阶乘和逆元的表就ok

这里有一种线性递推求逆元的方法,mark一下
假设已知[1,i-1]模p意义下的逆元,现在要求i模p意义下的逆元
k=pik=⌊pi⌋j=pkij=p−ki,那么有ki+j0(modp)ki+j≡0(modp)
两边同乘j、i的逆元有kj1+i10(modp)kj−1+i−1≡0(modp)
随便移项i1kj1(modp)i−1≡−kj−1(modp)
写成递推柿子就是inv[i]=(p-p/i)*inv[p%i]%p
就是酱

Code


#include <stdio.h>
#include <string.h>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

typedef long long LL;
const int N=10000005;

int prime[N/10],MOD;
bool not_prime[N+1];
LL fac[N+1],inv[N+1],ans[N+1];

void pre_work() {
    rep(i,2,N) {
        if (!not_prime[i]) prime[++prime[0]]=i;
        for (int j=1;i*prime[j]<=N&&j<=prime[0];j++) {
            not_prime[i*prime[j]]=1;
            if (i%prime[j]==0) break;
        }
    }
    fac[0]=1; rep(i,1,N) fac[i]=fac[i-1]*i%MOD;
    inv[1]=1; rep(i,2,N) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
    // rep(i,1,N) inv[i]=ksm(i,MOD-2)%MOD;
    int now=1;
    ans[1]=1;
    rep(i,2,N) {
        ans[i]=ans[i-1];
        while (prime[now]<=i&&now<=prime[0]) {
            ans[i]=ans[i]*(prime[now]-1)%MOD*inv[prime[now]]%MOD;
            now++;
        }
    }
}

void solve(int n,int m) {
    printf("%lld\n", ans[m]*fac[n]%MOD);
}

int main(void) {
    int T; scanf("%d%d",&T,&MOD);
    pre_work();
    while (T--) {
        int n,m; scanf("%d%d",&n,&m);
        solve(n,m);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值