- P44's solution
-
P44's Solution
- @ 2025-9-5 22:24:57
题解是 GPT 写的。
1. 规则抽象成集合递推
设模数为 。
-
第一天被感染的集合记为
-
第 天被感染的集合记为 。
题意说:某人 在某天会被感染,当且仅当存在
-
(前一天感染过)
-
(第一天感染过)
使得
因此有递推:
其中 表示“集合乘法”:
$$A\otimes C = \{(x\cdot y)\bmod M \mid x\in A,\ y\in C\}$$又因为 ,所以
$$S_2 = B\otimes B,\quad S_3 = (B\otimes B)\otimes B = B^3,\ \dots$$最终得到:
这里 表示在模 下取 次集合乘法(允许重复选同一个元素,符合“可以重复感染”的含义)。
2. 关键:如何计算集合乘法
用一个长度为 的布尔数组表示集合(0/1):
-
A[i]=1表示 -
C[j]=1表示
则
D[(i*j)%M] = 1 当且仅当存在 i∈A, j∈C
朴素做法就是两层枚举 ,复杂度 。因为 ,,是可以接受的。
3. 由于 K 很大:对集合做快速幂
我们要算 。这是“幂”的结构,所以可以用二进制快速幂(Exponentiation by Squaring)。
关键点是需要一个“乘法单位元”集合:
-
在模乘法下单位元是数字
-
所以单位集合是
令:
-
R = {1}(答案集合,初始为单位元) -
Base = B
按 的二进制位从高到低(或从低到高)迭代:
-
每一步先做:
R = R ⊗ R(相当于指数翻倍/平方) -
如果当前位为 1:
R = R ⊗ Base
最后得到的 R 就是 ,也就是第 天会感染的所有编号。
你给的代码正是这个过程:
-
ans保存当前集合R -
先平方(两层循环用
ans[org]和ans[org]) -
如果该位在
k里为 1,再乘一次baza(即B)
4. 正确性简述
-
递推部分:由题目定义直接推出 ,归纳可得 。
-
快速幂部分:集合乘法满足结合律(本质来自整数乘法 mod 的结合律),因此二进制快速幂完全成立;从单位元 出发,按位累乘能得到精确的 。
5. 复杂度
-
集合乘法一次:
-
快速幂需要约 次平方 + 若干次乘底
-
总复杂度:
在 时大约是 级别,C++ 里可行。
-
空间:
6. 输出
最终集合里为 1 的下标按从小到大输出即可。若 ,则 为空,任何乘法都得空集,输出空行也符合题意。
#include <bits/stdc++.h>
using namespace std;
// Time complexity: O(max_mod * max_mod * log(k))
const int max_mod = 1500;
char baza[max_mod];
char ans[2][max_mod], org = 0;
int mod;
long long k;
void load() {
int n, t;
scanf("%lld%d%d", &k, &mod, &n);
for (int j = 0; j < n; ++j)
scanf("%d", &t) && (baza[t] = 1);
}
void solve() {
ans[org][1] = 1;
for (unsigned long long mask = (1ll<<60); mask; mask >>=1) {
memset(ans[!org], 0, mod);
for (int i = 0; i < mod; ++i)
for (int j = 0; j < mod; ++j)
if (ans[org][i] && ans[org][j])
ans[!org][ (i*j) % mod ] = 1;
org = !org;
if (mask & k) {
memset(ans[!org], 0, mod);
for (int i = 0; i < mod; ++i)
for (int j = 0; j < mod; ++j)
if (ans[org][i] && baza[j])
ans[!org][ (i*j) % mod ] = 1;
org = !org;
}
}
for (int j = 0; j < mod; ++j) {
if (ans[org][j])
printf("%d ", j);
}
printf("\n");
}
int main() {
load();
solve();
return 0;
}