题解 新取模运算 [HUSTFC 2023][洛谷P9774]

背景

没想到自己高二时出的题目,居然真的有一天能被搬上正式比赛。
难度不高,没有用到什么高深算法,自以为是一道不错的签到题。

题目

题目描述

在这道题中,我们定义一个新的运算符号 \oplus 并将其称为新取模运算。

当计算 xyx \oplus y 时,如果 xx 不是 yy 的倍数,则得到 xx 除以 yy 的余数; 否则令 xx 不断除以 yy 直到 xx 不再是 yy 的倍数,假设它为 xx',然后得到 xx' 除以 yy 的余数。例如,45=44\oplus 5=4205=420\oplus 5=41005=4100\oplus 5=4

给定一个质数 pp,接下来会有多组询问,对于每次询问会给出一个整数 nn,你需要计算出 n!pn!\oplus p 的值。其中 n!n!nn 的阶乘,即所有小于等于 nn 的正整数的乘积。

输入格式

第一行包含两个整数 T (1T105)T\ (1\le T\le 10^5)p (2p106)p\ (2\le p\le 10^6),分别表示询问的次数和给定的质数。

接下来 TT 行,每行包含一个整数 n (1n1018)n\ (1\le n\le 10^{18}),含义如题目所述。

输出格式

对于每次询问,输出一行包含一个整数,即 n!pn!\oplus p 的值。

输入输出样例 #1

输入 #1

3 7
11
45
14

输出 #1

4
1
2

输入输出样例 #2

输入 #2

2 10007
1919
810

输出 #2

3152
3679

解析

思路

高二时出的题目,恍惚间已经四年了啊。

f(n)f(n) 为题目意义下 n!pn!\oplus p 的结果。注意到 pp 很小,可以预处理出 k!k!pp 常规意义下取模的结果 g(k)g(k),其中 1k<p1\le k<p.

考虑 1,2,3,,n1,2,3,\ldots,n 的组成,按照是否是质数 pp 的倍数分为两类。

k=nmodpk=n\bmod p.

pp11 pp22 \cdots ppkk \cdots ppp1p-1 pp00
11 22 \cdots kk \cdots p1p-1 pp
p+1p+1 p+2p+2 \cdots p+kp+k \cdots 2p12p-1 2p2p
\cdots \cdots \cdots \cdots \cdots \cdots \cdots
\cdots \cdots \cdots \cdots \cdots \cdots npp\lfloor\frac{n}{p}\rfloor p
npp+1\lfloor\frac{n}{p}\rfloor p+1 npp+2\lfloor\frac{n}{p}\rfloor p+2 \cdots nn

首先考虑不为 pp 的倍数的那些数的贡献,由于这些数都不是质数 pp 的倍数,他们相乘的结果自然也不是 pp 的倍数,直接相乘取模即可。

注意到这些数对 pp 取模的结果由np\lfloor \frac{n}{p}\rfloor{1,2,3,,p1}\{1,2,3,\ldots,p-1\} 与一组 {1,2,3,,nmodp}\{1,2,3,\ldots,n\bmod p\} 组成,因此贡献就是 g(p1)npg(nmodp)g(p-1)^{\lfloor \frac{n}{p}\rfloor}g(n\bmod p),这个值用快速幂可以直接算出来,最后乘到答案上就行。

然后考虑 pp 的倍数们的贡献,按照定义,对于其中的每一个数,我们都先让他除以 pp,不然正常取模就是 00 了。也就是说,{p,2p,3p,,npp}\{p,2p,3p,\cdots,\lfloor\frac{n}{p} \rfloor p\} 的贡献,完全等价于 {1,2,3,,np}\{1,2,3,\cdots,\lfloor\frac{n}{p} \rfloor\} 的贡献,那么这个值不正是 f(np)f(\lfloor\frac{n}{p} \rfloor) 吗,我们要解决的问题转化为了一个形式完全一致,但规模只有原先 1p\frac{1}{p} 的子问题,递归解决即可。

复杂度 O(p+Tlogn)O(p+T\log n).

代码

#include <cstdio>
#include <iostream>
using namespace std;
const int maxn = 1000005;
int T, p, fac[maxn];
long long n;
long long power(long long x, long long k) {
	long long ans = 1;
	while(k) {
		if(k & 1) ans = ans * x % p;
		x = x * x % p;
		k >>= 1;
	}
	return ans;
}
int solve(long long n) {
	if(n < p) return fac[n];
	return power(fac[p-1], n/p) * fac[n%p] % p * solve(n/p) % p;
}
int main() {
	cin >> T >> p;
	fac[0] = 1;
	for(int i = 1; i < p; ++i)
		fac[i] = (long long)fac[i-1] * i % p;
	while(T--) {
		cin >> n;
		cout << solve(n) << endl;
	}
}

p.s

由威尔逊定理 (p1)!1(modp)(p-1)!\equiv -1\pmod p 可知 g(p1)=1g(p-1)=-1,这样就只用考虑 1-1 的多少次方而无需快速幂了。

#include <cstdio>
#include <iostream>
using namespace std;
const int maxn = 1000005;
int T, p, fac[maxn];
long long n, ans;
int main() {
	cin >> T >> p;
	fac[0] = 1;
	for(int i = 1; i < p; ++i)
		fac[i] = (long long)fac[i-1] * i % p;
	while(T--) {
		cin >> n;
		ans = 1;
		while(n) {
			ans = ans * fac[n%p] % p;
			if(n/p & 1) ans = p - ans;
			n /= p;
		}
		cout << ans << endl;
	}
}

此外,这个题目的点子源于一道证明题,大致写一下:

题目求,n!n! 中去除所有素因子 pp,求对 pp 取模的结果。

n!n! 表示为 pp 进制后,从低位往高位第一个非 00 数字

为了与传统意义下的 lowbit\text{lowbit} 相互区别,我们不妨把 (x)p(x)_p 从低位往高位第一个非 00 数字记为 lobit(x)\text{lobit}(x).

显然,lobit(n!)\text{lobit(}n!)1,2,3,,n1,2,3,\ldots,n 中每个数字的 lobit\text{lobit} 有关。

更准确的说,是 i=1nlobit(i)modp\prod_{i=1}^n\text{lobit}(i)\bmod p.

尽管 nn 的可能相当大,但是 logpn\log_pn 不超过 6363,这个 pp 进制数的位数很小。

我们可以考虑从低到高每一位是否作为 lobit\text{lobit} 产生贡献。

(n)p=(ama2a1a0)p(n)_p=(a_m\cdots a_2a_1a_0)_p,其中

ak=npkmodpa_k=\left\lfloor\frac{n}{p^k}\right\rfloor\mod p

以最低位为例,我们发现,最低位会依次取 1,2,...,p11,2,...,p-1,若干个循环后,取 1,2,...,a01,2,...,a_0 结束。

进一步的,循环回合数就是 (ama2a1)p(a_m\cdots a_2a_1)_p,即 n/p\displaystyle\lfloor n/p\rfloor.

那么最低位作为 lobit\text{lobit} 的贡献是多少呢?

(p1)!npa0!modp\displaystyle{(p-1)!}^{\lfloor\frac{n}{p}\rfloor}a_0! \mod p

=(1)npa0!modp=\displaystyle(-1)^{\lfloor\frac{n}{p}\rfloor}a_0! \mod p

则总贡献为

(1)n/p+n/p2++n/pma0!a1!am!modp\displaystyle(-1)^{\lfloor n/p\rfloor+\lfloor n/p^2\rfloor+\cdots+\lfloor n/p^m\rfloor}a_0!a_1!\cdots a_m!\mod p

=(1)εp(n)i=0mai!modp=\displaystyle(-1)^{\varepsilon_p{(n)}}\prod_{i=0}^m a_i!\mod p

这个等式还是蛮有趣的。