NKOJ 3683 沙拉公主的困惑
时间限制 : - MS 空间限制 : 265536 KB
评测说明 : 时限:1500ms
问题描述
大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。
输入格式
第一行为两个整数T,R。R<=10^9+10,T<=10000,表示该组中测试数据数目,R为模后面T行,每行一对整数N,M,见题目描述 m<=n
输出格式
共T行,对于每一对N,M,输出1至N!中与M!互质的数的数量对R取模后的值
样例输入 1
1 11
4 2
样例输出 1
1
样例输入 2
5 4421
4 1
4 3
6 2
6 3
6 6
样例输出 2
24
8
360
240
192
思路:
根据题目描述,即求n!中与m!互质的数的数目;
(结论1:)若[1,a]中有k个数与a互质,即phi[a]=k;则[k* a+1,k*a+a]中与a互质的数的数目也为phi[a];
则,可得:对于两个正整数x和y,如果x整除y,那么1~y中与x互质的数的个数为(y/x)* phi[x]。
因为n!为m!的整倍数,题目即转换为求n!/m!*phi[m!]%r;
因为phi[m!]=m!(1-1/p1) (1-1/p2)…… *(1-1/pk);
则原式= n!(1-1/p1) (1-1/p2)…… *(1-1/pk)%r
(结论2:)(1-1/p1)%r* (1-1/p2)%r……* (1-1/pk)%r==(p1-1)/p1%r* (p2-1)/p2%r……* (pk-1)/pk%r==(p1-1)* p1(逆元)%r* (p2-1)* p2(逆元)%r……* (pk-1)* pk(逆元)%r
所以原式=n!%r* (p1-1)* p1(逆元)%r* (p2-1)* p2(逆元)%r……* (pk-1)*pk(逆元)%r
上式中p1~pk为m!的质因数,且m!的因数为 1、2、3…m
所以:(结论3:)m!的所有质因数就是所有不超过m的质数
即p1~pk为m以内的质数;
优化:
1、因为有多组数据,所以用前缀数组预处理,处理上限为所有m、n中最大的一个,而并非题目上限10000000
预处理n!%r及(p1-1)* p1(逆元)%r* (p2-1)* p2(逆元)%r……* (pk-1)*pk(逆元)%r,因为求最大的一组时m、n时需递推计算。若不预处理则会重复计算。
2、求逆元用拓欧(费马超时QAQ)
3、(结论4:)long long 范围的取模乘法才用快速乘法,int范围转换为long long即可:(ll)a*b%c(不然也是超时QAQ)
代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int need=10000003;
#define ll long long
int t,r,nn=0,mm=0,an[need],am[need];
/*an[i]表示n!%r,am[i]表示小于i的所有质数p1、p2、……pk的(p1-1)* p1(逆元)%r* (p2-1)* p2(逆元)%r……* (pk-1)*pk(逆元)%r*/
int n[need],ni[need];
bool zhi[need];
void gcd(int a,int b,int &x,int &y)
{
if(b==0)
{
x=1,y=0;
return;
}
int x1,y1;
gcd(b,a%b,x1,y1);
x=y1,y=(x1-(a/b*y1));
}
int ni_(int a)
{
int x,y;
gcd(a,r,x,y);
return (ll)((x+r)%r)*(a-1)%r;
/*返回(a-1)*a(逆元)%r*/
}
int main()
{
scanf("%d%d",&t,&r);
for(int i=1;i<=t;i++)
{
scanf("%d%d",&an[i],&am[i]);
if(an[i]>nn) nn=an[i];
if(am[i]>mm) mm=am[i];
}
int cnt=0;
n[1]=1;
for(int i=2;i<=nn;i++) n[i]=((ll)n[i-1]*i)%r;
ni[1]=1;
for(int i=2;i<=mm;i++)
{
if(zhi[i]==false)
{
ni[i]=(ll)ni_(i)*ni[i-1]%r;
for(int j=i*2;j<=mm;j+=i) zhi[j]=true;
}
else ni[i]=ni[i-1];
}
for(int i=1;i<=t;i++)
{
int ans=(ll)n[an[i]]*ni[am[i]]%r;
printf("%d\n",ans);
}
}