【bzoj2186】[Sdoi2008]沙拉公主的困惑 数论 线性筛逆元

Description

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

Input

第一行为两个整数T,R。R<=10^9+10,T<=10000,表示该组中测试数据数目,R为模后面T行,每行一对整数N,M,见题目描述 m<=n

Output

共T行,对于每一对N,M,输出1至N!中与M!素质的数的数量对R取模后的值

Sample Input

1 11

4 2

Sample Output

1

数据范围:

对于100%的数据,1 < = N , M < = 10000000

HINT

Source

数论


题目要求区间[1,n!]中有多少数和m!互质。m<=n<=1000w。答案模mod。

先说一下,下面所有说的知识全部来自于各神犇的博客…

首先,若x和m!互质,则x+m!也与m!互质,可以由辗转相除法得知。

若一个数p和m!互质,则[1,n!]中 p+km! 一共出现了 n!/m! 次。

所以统计小于m!的与m!互质的数即可,即 ϕ(m!) 。答案就是:

ϕ(m!)n!m!

因为 ϕ(m!)=m!(11p)

所以答案就是:

n!(11p)

=n!p1p

这样预处理n!,再线性时间筛p的逆元,然后就可以预处理出来 p1p

下面说一下如何线性筛逆元。

讨论需要让模的数(p)是一个素数。

当然可以用exgcd求,如果不嫌慢的话。这个题好像足够?

t=p / i , k=p mod i

ti+k0(modp)

kti(modp)

1itk(modp)

记inv[i]为i的逆元。

inv[i]tinv[k](modp)

inv[i]=(ptinv[k]) mod p

inv[i]=(pp / i)inv[p mod i]

这样就能线性时间筛了。不过要注意 p是素数!!

codevs上这题很坑,p是合数,务必注意。不要问我怎么知道的。

感谢PoPoQQQ

数论题务必注意优化常数…

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;

const int SZ = 10000100;
const int MAXN = 10000010;

int mod;

int fac[SZ],pri[700010],a[SZ],inv[SZ];
bool vis[SZ];

void gen()
{
    for(int i = 2,tot = 0;i <= MAXN;i ++)
    {
        if(!vis[i]) pri[++ tot] = i;
        for(int j = 1;j <= tot && i * pri[j] <= MAXN;j ++)
        {
            vis[i * pri[j]] = 1;
            if(i % pri[j] == 0) break;
        }
    }   

    fac[1] = 1; inv[1] = 1; a[1] = 1;
    for(int i = 2;i <= MAXN;i ++)
    {
        fac[i] = ((LL)fac[i - 1] * (i % mod)) % mod;

        inv[i] = ((LL)(mod - mod / i) * (LL)inv[mod % i]) % mod;

        if(!vis[i]) 
            a[i] = (((LL)a[i - 1] * ((i - 1) % mod)) % mod * (LL)inv[i % mod]) % mod;
        else 
            a[i] = a[i - 1];
    }
}

void scan(int &n)
{
    n = 0;
    char a = getchar();
    bool flag = 0;
    while(a < '0' || a > '9') {if(a == '-') flag = 1;a = getchar();}
    while('0' <= a && a <= '9') {n = (n << 3) + (n << 1) + a - '0';a = getchar(); }
    if(flag) n = -n;
}

int main()
{
    int T;
    scan(T);
    scan(mod);
    gen();
    while(T --)
    {
        int n,m;
        scan(n);scan(m);
        printf("%d\n",(int)(((LL)fac[n] * (LL)a[m]) % mod));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值