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+r\equiv0(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} \rfloor*(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\n",jc[n]%M*ans[m]%M);  //直接输出答案 
	}
	return 0;
}
posted @ 2020-08-04 21:00  暗い之殇  阅读(137)  评论(0编辑  收藏  举报