题目描述
大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为1到N的阶乘,但是,政府只发行编号与M!互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。现在,请你帮助沙拉公主解决这个问题,由于可能张数非常大,你只需计算出对R取模后的答案即可。R是一个质数。//codevs这里有坑,R是合数
输入输出格式
输入格式:
第一行为两个整数T,R。R<=10^9+10,T<=10000,表示该组中测试数据数目,R为模 后面T行,每行一对整数N,M,见题目描述 m<=n
输出格式:
共T行,对于每一对N,M,输出1至N!中与M!素质的数的数量对R取模后的值
输入输出样例
1 11 4 2
1 数据范围: 对于100%的数据,1 < = N , M < = 10000000
解题思路
http://www.cnblogs.com/yangyaojia/p/6434611.html、
http://blog.csdn.net/loi_dqs/article/details/50520309
吐槽
这题一题能抵三题做了。
不想被卡常就离线吧,筛法筛到最大的n即可,否则卡常、卡内存太严重了,我的好不容易洛谷ac,跑了6s。
求逆元可以线性递推,也可以扩欧。
popoqqq大神博客的代码(https://www.luogu.org/record/show?rid=2565704)、黄学长博客(https://www.luogu.org/record/show?rid=2565713)里线性递推逆元的代码都在洛谷爆零,不知为啥。
我的代码离线int会溢出,估计是筛法出问题,还不够优化,于是递推求逆元的数组开不下了,就用扩欧吧。
卡一波常1.3s跑完(BZOJ不开O2跑了2.9s)。
源代码
6s的,求逆元用线性递推(BZOJ TLE)
#include<cstdio> #include<cstring> #include<algorithm> int T,R,n,m; long long jc[10000010]={0};//阶乘取模 long long inv[10000010]={0};//逆元 bool not_prime[10000010]={0};//筛子 long long ans[10000010]={0};//pi累乘 void init() { inv[1]=1; for(int i=2;i<=10000000&&i<R;i++) inv[i]=(R-R/i)%R*inv[R%i]%R; ans[1]=1;jc[1]=1; for(int i=2;i<=10000000;i++) { jc[i]=jc[i-1]*i%R; if(!not_prime[i]) { ans[i]=ans[i-1]*(i-1)%R*inv[i%R]%R; for(int j=i<<1;j<=10000000;j+=i) not_prime[j]=1; } else ans[i]=ans[i-1]; } } int main() { scanf("%d%d",&T,&R); init(); while(T--) { scanf("%d%d",&n,&m); printf("%lld\n",(long long)(jc[n])*(long long)(ans[m])%R); } return 0; }
1.3s的,求逆元用扩欧
#include<cstdio> #include<algorithm> int T,R,maxn=-1; long long jc[10000010]={0};//阶乘取模 //int inv[10000010]={0};//逆元 bool not_prime[10000010]={0};//筛子 long long ans[10000010]={0};//pi累乘 int n[10000010]={0},m[10000010]={0}; void exgcd(int a,int b,int &x,int &y) { if(b==0){x=1;y=0;return;} exgcd(b,a%b,x,y); int t=x;x=y;y=t-a/b*y; } inline int getine(int t) { int x,y; exgcd(t,R,x,y); return (x%R+R)%R; }//扩欧求逆元 inline void Read(int &in){ static char ch; for(ch=getchar();ch>'9'||ch<'0';ch=getchar()) ; for(in=0;ch>='0'&&ch<='9';ch=getchar()) in=in*10+ch-'0'; } void init() { /*inv[1]=1; for(int i=2;i<=maxn&&i<R;i++) inv[i]=(R-R/i)%R*inv[R%i]%R;*///线性递推求逆元 ans[1]=1;jc[1]=1; for(int i=2;i<=maxn;i++) { jc[i]=jc[i-1]*i%R; if(!not_prime[i]) { ans[i]=ans[i-1]*(i-1)%R*getine(i%R)%R; for(int j=i<<1;j<=maxn;j+=i) not_prime[j]=1; } else ans[i]=ans[i-1]; } } int main() { Read(T);Read(R); for(register int i=1;i<=T;i++) Read(n[i]),Read(m[i]),maxn=std::max(maxn,n[i]); init(); for(register int i=1;i<=T;i++) printf("%lld\n",jc[n[i]]*ans[m[i]]%R); return 0; }
洛谷记录里最优的代码,用户ID小小莫。Orz,洛谷跑了0.9s,BZOJ不开O2跑了2.6s,素数筛法值得学习。
#include <cstdio> #include <algorithm> using namespace std; static const int maxm = 10000 + 50; static const int maxx = 10000000 + 50; typedef long long LL; bool Notprime[maxx]; int inv[maxx],phi[maxx],fac[maxx],prime[maxx]; int T,n[maxm],m[maxm],p,cnt,tot; void Read(int &); inline void Linear_shaker(){ int maxn = tot+1; fac[1]=1;inv[1]=1;phi[1]=1; for(register int i=2;i<maxn;i++){ if(!Notprime[i]) prime[++cnt]=i; for(register int j=1;j<=cnt && i*prime[j]<maxn;j++){ Notprime[i*prime[j]]=1; if(i%prime[j]==0) break; } } for(register int i=2;i<=maxn;i++){ fac[i] = ((LL)fac[i-1]*i)%p; if(i<p) inv[i] = (LL)(p-p/i)*inv[p%i]%p; if(!Notprime[i]) phi[i] = (LL)phi[i-1]*(i-1)%p*inv[i%p]%p; else phi[i] = phi[i-1]; } } int main(){ Read(T),Read(p); for(register int i=1;i<=T;i++) Read(n[i]),Read(m[i]),tot = max(tot,n[i]); Linear_shaker(); for(register int i=1;i<=T;i++) printf("%lld\n",(LL)fac[n[i]]*phi[m[i]]%p); return 0; } void Read(int &in){ static char ch; for(ch=getchar();ch>'9'||ch<'0';ch=getchar()) ; for(in=0;ch>='0'&&ch<='9';ch=getchar()) in=in*10+ch-'0'; }