【题解】Luogu-P2155

P2155 [SDOI2008] 沙拉公主的困惑

Description \text{Description} Description

  • 给定 t , r t,r t,r,有 t t t 次询问,每次给定 n , m n,m n,m,请求出 [ 1 , n ! ] [1,n!] [1,n!] 中与 m ! m! m! 互质的数的数量,答案对 r r r 取模.

  • 对于 100 % 100\% 100% 的数据, 1 ≤ m ≤ n ≤ 1 0 7 , 1 ≤ t ≤ 1 0 4 , 2 ≤ r ≤ 1 0 9 + 10 1\le m\le n\le 10^7,1\le t\le 10^4,2\le r\le 10^9+10 1mn107,1t104,2r109+10 r r r 为质数.

Solution \text{Solution} Solution

首先因为 m ≤ n m\le n mn,所以 m ! ∣ n ! m!\mid n! m!n!,则与 m ! m! m! 互质的有 n ! m ! ⋅ φ ( m ! ) \dfrac{n!}{m!}\cdot\varphi(m!) m!n!φ(m!) 个.

我们来整理一下:
n ! m ! ⋅ φ ( m ! ) = n ! m ! ⋅ ( m ! ⋅ ∏ 质 数 p ∣ m ! p − 1 p ) = n ! ⋅ ∏ 质 数 p ∣ m ! p − 1 p \begin{aligned} \dfrac{n!}{m!}\cdot\varphi(m!) &=\dfrac{n!}{m!}\cdot(m!\cdot\prod\limits_{质数p\mid m!}\dfrac{p-1}{p})\\ &=n!\cdot\prod\limits_{质数p\mid m!}\dfrac{p-1}{p} \end{aligned} m!n!φ(m!)=m!n!(m!pm!pp1)=n!pm!pp1
因为 m ! = 1 × 2 × ⋯ × m m!=1\times 2\times \cdots\times m m!=1×2××m,所以质数 p p p 要整除 m ! m! m! 实际上就是要满足质数 p ≤ m p\le m pm.

所以就是
n ! ⋅ ∏ 质 数 p ≤ m p − 1 p = n ! ⋅ ∏ 质 数 p ≤ m ( p − 1 ) ∏ 质 数 p ≤ m p n!\cdot\prod\limits_{质数p\le m}\dfrac{p-1}{p}\\ =\dfrac{n!\cdot\prod\limits_{质数p\le m}(p-1)}{\prod\limits_{质数p\le m}p} n!pmpp1=pmpn!pm(p1)
看起来预处理阶乘,前缀积和逆元,然后询问时三个一乘就完事了.

求连续 n n n 个数的逆元见 逆元.

具体地,用 j c x jc_x jcx 表示 x ! x! x! p h i x phi_x phix 表示 ∏ 前 x 个 质 数 p ( p − 1 ) \prod\limits_{前x个质数p}(p-1) xp(p1)(好吧我知道这不是 p h i phi phi 但我们暂且叫它 p h i phi phi), i n v x inv_x invx 表示 i n v ( ∏ 前 x 个 质 数 p p ) inv\left(\prod\limits_{前x个质数p}p\right) inv(xpp).

查询时用 upper_bound ⁡ \operatorname{upper\_bound} upper_bound 找到质数中第一个大于 m m m 的,则在这之前的所有质数必然都是小于等于 m m m 的,记它的位置是 y y y x = y − 1 x=y-1 x=y1,然后答案就是 ( j c n ⋅ p h i x ⋅ i n v x )   m o d   r (jc_n\cdot phi_x\cdot inv_x)\bmod r (jcnphixinvx)modr.

scanf("%d%d", &n, &m);
int x = upper_bound(p + 1, p + p[0] + 1, m) - (p + 1);
printf("%d\n", 1ll * jc[n] * phi[x] % r * inv[x] % r);

然而兔队提供了 hack \text{hack} hack 数据, hack \text{hack} hack 的原理是,当分子与分母中含有相同个因数 r r r 时,正常计算会约分掉,但程序中如果把三个乘起来的话这三个中有 0 0 0,算出来就是 0 0 0,这是错的.

所以重点在于考虑如何处理这种情况.

因为 n ≥ m n\ge m nm,所以 n ! n! n! 中因数 r r r 的个数一定大于等于 ∏ 质 数 p ≤ m p \prod\limits_{质数p\le m}p pmp 中因数 r r r 的个数,即分子的 r r r 个数 ≥ \ge 分母的 r r r 个数,那么就不需要考虑 ∏ 质 数 p ≤ m ( p − 1 ) \prod\limits_{质数p\le m}(p-1) pm(p1) 中的因数 r r r 被约去的情况,这部分就可以直接乘了.

n ! n! n! 中因数 r r r 的个数比 ∏ 质 数 p ≤ m p \prod\limits_{质数p\le m}p pmp 多的话,约分后还是 r r r 的倍数,输出 0 0 0.

否则说明它们的因数 r r r 的个数一定相等!那么所有 r r r 都会被约掉.

在求阶乘和逆元时遇到 r r r 就不乘.

这样算还得特判 m = 1 m=1 m=1 的情况,答案为 j c n jc_n jcn.

Code \text{Code} Code

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 1e7 + 5;

int p[MAXN];
bool vis[MAXN];

void get_prime()
{
	for (int i = 2; i < MAXN; i++)
	{
		if (!vis[i])
		{
			p[++p[0]] = i;
		}
		for (int j = 1; j <= p[0] && i * p[j] < MAXN; j++)
		{
			vis[i * p[j]] = true;
			if (i % p[j] == 0)
			{
				break;
			}
		}
	}
}

int t, r, n, m;

int ksm(int a, int b)
{
	int base = a, ans = 1;
	while (b)
	{
		if (b & 1)
		{
			ans = (long long)ans * base % r;
		}
		base = (long long)base * base % r;
		b >>= 1;
	}
	return ans;
}

int jc[MAXN];

void get_jc()
{
	jc[0] = 1;
	for (int i = 1; i < MAXN; i++)
	{
		if (i != r)
		{
			jc[i] = (long long)jc[i - 1] * i % r;
		}
		else
		{
			jc[i] = jc[i - 1];
		}
	}
}

int phi[MAXN];

void get_phi()
{
	phi[0] = 1;
	for (int i = 1; i <= p[0]; i++)
	{
		phi[i] = (long long)phi[i - 1] * (p[i] - 1) % r;
	}
}

int pro[MAXN], inv[MAXN];

void get_inv()
{
	pro[0] = 1;
	for (int i = 1; i <= p[0]; i++)
	{
		if (p[i] != r)
		{
			pro[i] = (long long)pro[i - 1] * p[i] % r;
		}
		else
		{
			pro[i] = pro[i - 1];
		}
	}
	inv[p[0]] = ksm(pro[p[0]], r - 2);
	for (int i = p[0] - 1; i >= 1; i--)
	{
		if (p[i + 1] != r)
		{
			inv[i] = (long long)inv[i + 1] * p[i + 1] % r;
		}
		else
		{
			inv[i] = inv[i + 1];
		}
	}
}

int main()
{
	scanf("%d%d", &t, &r);
	get_prime();
	get_jc();
	get_phi();
	get_inv();
	while (t--)
	{
		scanf("%d%d", &n, &m);
		if (n / r > m / r)
		{
			puts("0");
			continue;
		}
		else if (m == 1)
		{
			printf("%d\n", jc[n]);
			continue;
		}
		int x = upper_bound(p + 1, p + p[0] + 1, m) - (p + 1);
		printf("%d\n", (long long)jc[n] * phi[x] % r * inv[x] % r);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值