P2155 [SDOI2008]沙拉公主的困惑
题目描述
大富翁国因为通货膨胀,以及假钞泛滥,政府决定推出一项新的政策:现有钞票编号范围为 \(1\) 到 \(N\) 的阶乘,
但是,政府只发行编号与 \(M!\) 互质的钞票。房地产第一大户沙拉公主决定预测一下大富翁国现在所有真钞票的数量。
现在,请你帮助沙拉公主解决这个问题,由于数量可能非常大,你只需计算出答案对 \(R\) 取模后的结果即可。
输入格式
第一行为两个整数 \(T\) 和 \(R\),其中 \(T\) 为该组中测试数据数目,\(R\) 为模数。
接下来 \(T\) 行,每行一对整数 \(N\) 和 \(M\),具体意义见题目描述。
输出格式
共 \(T\) 行,对于每一对 \(N\) 和 \(M\),输出 \([1,N!]\) 中与 \(M!\) 互质的数的数量对 \(R\) 取模后的值。
输入输出样例
输入 #1
1 11
4 2
输出 #1
1
说明/提示
对于 $ 100 %$ 的数据,\(1\leq M\leq N\leq 10^71\) \(1\leq T\leq 10^4\),\(2\leq R\leq 10^9+10\) 且 R 为质数。
首先,题目让我们求得是这个柿子
\(\displaystyle\sum_{i=1}^{n!} [gcd(i,m!) == 1]\)
最大公约数有这样一个性质 若\(gcd(a,b) == 1\) 则\(gcd(a+b,b) == 1\)
这样,我们可以把它分成\(n! \over m!\)个块,对每块求一个贡献,在加起来就是答案
因为\(n! > m!\)所以我们分的一定是整块
那么我们的柿子可以化成这样
\({n!\over m!} \times \displaystyle\sum_{i=1}^{m!} [gcd(i,m!) == 1]\)
这个柿子是不是有点眼熟。
后面那个不就是欧拉函数吗?(大雾)
这样我们的柿子又能再化简一次变成
\({n!\over m!} \times \displaystyle\sum_{i=1}^{m!} \varphi(m!)\)
这时候,我们发现还是不能暴力枚举,所以我们需要在优化一下。
考虑欧拉函数有个计算式
\(\varphi(n)\) = \(n \times {(p_1-1)\over p_1} \times {(p_2-1) \over p_2} ··· \times {(p_n-1) \over p_n}\)
所以我们的柿子可以化为
\(n! \times {(p_1-1)\over p_1} \times {(p_2-1) \over p_2} ··· \times {(p_n-1) \over p_n}\)(p为1到m的质因数)
这样就可以很快的利用欧拉筛把后面的那一坨求出来。
这样我们可以做到O(1)的询问和O(m)的预处理
吸个氧稳过
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
LL t,p,n,m,tot,ans;
LL jz[10000050],prime[5000050],inv[10000050],sum[10000050];
bool check[10000050];
inline LL read()
{
LL s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s= s * 10+ch - '0'; ch = getchar();}
return s * w;
}
void YYCH()
{
check[1] = 1; sum[1] = 1; inv[1] = 1;
for(int i = 2; i <= 10000010; i++)//筛出1-m中的质数
{
if(!check[i])
{
prime[++tot] = i;
}
for(int j = 1; j <= tot && i * prime[j] <= 10000010; j++)
{
check[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
for(int i = 2; i <= 10000010; i++) inv[i] = (p- p/i) * inv[p % i] % p;//线性处理逆元
for(int i = 2; i <= 10000010; i++)
{
if(!check[i]) sum[i] = (sum[i-1] % p * (i-1) % p * inv[i]) % p;//求出每个数的欧拉函数
else sum[i] = sum[i-1];
}
}
int main()
{
t = read(); p = read(); jz[0] = 1; YYCH();
for(int i = 1; i <= 10000010; i++) jz[i] = (jz[i-1] * i) % p;//处理阶乘
while(t--)
{
n = read(); m = read();
printf("%lld\n",jz[n] * sum[m] % p);//O(1)回答
}
return 0;
}