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!)=m!∗∏(1−1p)
所以答案就是:
这样预处理n!,再线性时间筛p的逆元,然后就可以预处理出来 ∏p−1p
下面说一下如何线性筛逆元。
讨论需要让模的数(p)是一个素数。
当然可以用exgcd求,如果不嫌慢的话。这个题好像足够?
设 t=p / i , k=p mod i 。
记inv[i]为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;
}