P2155 [SDOI2008]沙拉公主的困惑


数学题都是屑题。

题解

题目要求 (sum_{i=1}^{n!}gcd(i,m!)=1) 这个式子。
我们先想简单的,我们会求 (sum_{i=1}^{m!}gcd(i,m!)=1),即 (φ(m!))
考虑到 (m<=n),所以 (n!) 一定是 (m!) 的倍数,所以我们可以将 (n!) 分成 (frac{n!}{m!})(m!),而每一段有 (φ(m!)) 个数与 (m!) 互质,所以总共有 (frac{n!}{m!}*φ(m!)) 个数与 (m!) 互质,答案就是 (frac{n!}{m!}*φ(m!))
上面的推理依据于一个定理:若 (gcd(a,b)=1),则 (gcd(a+b*k,b)=1)
证明:
我们可以利用 (gcd) 的性质来证:(gcd(a,b)=gcd(b,a\%b)=gcd(a\%b,b)),那么我们反着推回去即可得到:(gcd(a,b)=gcd(a+b*k,b)),证毕。
还有一个定理:若 (m=p_1^{q_1}p_2^{q_2}p_3^{q_3}...p_k^{q_k}(p_1,p_2,...,p_k)均为质数()),则 (m!) 的质因子仅有 (p_1,p_2,p_3...p_k)
这样我们再对答案式子进行化简:
(ans=frac{n!}{m!}*φ(m!)=frac{n!}{m!}*m!*frac{p_1-1}{p_1}*frac{p_2-1}{p_2}*...*frac{p_k-1}{p_k}=n!*prod_{i=1}^{k}frac{p_i-1}{p_i})
然后我们可以预处理 (1e7) 范围内的 (n!)(prod_{i=1}^{k}frac{p_i-1}{p_i}),对于每次询问直接 (O(1)) 输出答案 。
(jc[i]) 表示 (i!) 是多少。
(inv[i]) 表示 (i) 的逆元是多少。
(ans[i]) 表示当上面的 (m=i) 时,(prod_{i=1}^{k}frac{p_i-1}{p_i}) 是多少。
求逆元的话需要线性的复杂度,这里再复习一下怎么求(主要是我也忘了(qwq)):
我们设 (p=k*i+r(1<r<i<p))(k) 就是 (p/i) 的商,(r) 是余数。
则有 (k*i+requiv0(mod) (p)),两边同乘 (i^{-1}*r^{-1}) 得:
(k*r^{-1}+i^{-1}equiv0(mod) (p))
(i^{-1}equiv-k*r^{-1}(mod) (p))
(i^{-1}equiv-lfloor frac{p}{i} floor*(p) (mod) (i)^{-1} (mod) (p))

inv[1] = 1;
for(int i = 2; i < p; ++ i)
    inv[i] = (p - p / i) * inv[p % i] % p;

对于 (ans[i]) 的维护,我们在枚举 (i) 的时候如果发现 (i) 是质数,就让 (ans[i]=ans[i-1]*frac{i-1}{i});否则的话 (ans[i]=ans[i-1])
判断质数的过程我们可以用欧拉筛来实现。
时间复杂度 (O(n)),稍微有点卡常(....)
(Code:)

#pragma GCC optimize(1)                //手动开O(3)卡常qwq         
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio> 
#include<cmath>
#include<stack>
#define ll long long
using namespace std;
const ll N=1e7+5;
int read()
{
	char ch=getchar();
	int a=0,x=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') x=-x;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		a=(a<<1)+(a<<3)+(ch^48);
		ch=getchar();
	}
	return a*x;
}
int T,n,m,sum;
ll M,jc[N],inv[N],ans[N],prime[N];
bool is_prime[N];
int main()
{
	T=read();M=(ll)read();
	inv[0]=1;inv[1]=1;
	ans[0]=1;ans[1]=1;
	jc[0]=1;jc[1]=1;
	for(int i=2;i<=1e7;i++)                //预处理1e7内的信息 
	{
		jc[i]=jc[i-1]*i%M;             //朴素算阶乘 
		inv[i]=inv[M%i]*(M-M/i)%M;     //线性求逆元 
		ans[i]=ans[i-1]%M;             //维护ans数组 
		if(is_prime[i]==0)             //如果i是质数的话,要多乘一个(i-1)/i 
		{
			prime[++sum]=i;            
			ans[i]=ans[i]%M*(i-1)%M*inv[i]%M;  //不能直接除以i,要乘i的逆元 
		}
		for(int j=1;j<=sum&&prime[j]*i<=1e7;j++)   //欧拉筛素数的过程 
		{
			is_prime[i*prime[j]]=1;
			if(i%prime[j]==0) break;
		}
	}
	while(T--)
	{
		n=read();m=read();
		printf("%lld
",jc[n]%M*ans[m]%M);  //直接输出答案 
	}
	return 0;
}
原文地址:https://www.cnblogs.com/xcg123/p/13435723.html