1 条题解
-
0
你好!我是你的OI教练。很高兴看到你挑战 GESP 五级的题目。五级题目通常开始涉及更深入的数论和算法优化知识。
这道题《因数分解》是数论中的经典入门题——质因数分解(Prime Factorization)。虽然题目逻辑不难,但在 的数据规模下,它考验的是你对时间复杂度的敏感度和数据类型的把控。
我们还是按老规矩,先拿出草稿纸,把解题思路拆解开来。
1. 读题关键词:你的“雷达”响了吗?
- :
- 红色警报:
int的最大值约为 。这个 远超int范围。 - 动作:必须使用
long long存储 。
- 红色警报:
- “质因数由小到大排列”:
- 暗示我们可以从 2 开始,尝试去除 。因为从小到大尝试,能整除的一定是质数(因为如果是合数,它的因子早在之前就被除干净了)。
- 格式要求:
*左右有空格,^左右没空格。- 这是经典的**“栅栏问题”**:第一个元素前不能打印
*,从第二个元素开始,在打印前先打印*。
2. 预备知识点
- 试除法(Trial Division):
- 要分解 ,不需要遍历到 ,只需要遍历到 即可。
- 原理:如果 有因数 和 使得 ,那么 和 中至少有一个小于等于 。
- 效率:。循环 100 万次,计算机在 10ms 内就能完成,绝对不会超时。
- 取模与除法:
while (n % i == 0):如果能整除,就一直除,统计次数(指数)。
3. 启发式教学:草稿纸上的推演
假设我们要分解 。
步骤 当前 N 尝试因子 i 能整除吗? 操作 累计输出 1 20 2 是 cnt++,2 10 cnt++,3 5 否 停止除2。 cnt=2。2^24 3 跳过 5 4 (其实不会遇到4) (因为4的因子2已被除尽) ... Loop结束 5 循环终止 此时 收尾 - 输出剩余的N 2^2 * 5关键逻辑: 循环条件是
i * i <= n。 循环结束后,如果 ,说明剩下的这个 就是最大的那个质因数(比如分解 22,循环到 结束,剩下 11,直接输出)。
4. 标准代码 (NOIP C++14)
/** * 题目:B3871 [GESP202309 五级] 因数分解 * 考点:质因数分解、试除法、格式化输出 * 难度:普及- / GESP五级 */ #include <iostream> #include <cmath> using namespace std; void solve() { // 关键点1:数据范围 10^12,必须用 long long long long n; cin >> n; // 这是一个标记,用来处理由 " * " 分隔的格式问题 // true 表示当前是输出的第一个项,不需要在前面加乘号 bool first = true; // 关键点2:试除法 // 只需要循环到 sqrt(n) 即可。注意 i*i <= n 可能会溢出,建议写成 i <= n/i 或者用 sqrt // 但因为 i 是 long long 且 n 最大 10^12,i*i 最大 10^12,不会溢出 long long for (long long i = 2; i * i <= n; i++) { // 如果 i 是 n 的因子 if (n % i == 0) { int cnt = 0; // 统计指数 // 将 i 除干净 while (n % i == 0) { cnt++; n /= i; } // 输出格式控制 if (!first) { cout << " * "; } cout << i; if (cnt > 1) { cout << "^" << cnt; } // 既然已经输出了第一项,后面就都不是第一项了 first = false; } } // 关键点3:收尾 // 如果循环结束后 n 仍然 > 1,说明剩下的 n 是一个大于 sqrt(原始n) 的质数 if (n > 1) { if (!first) { cout << " * "; } cout << n; // 注意:此时 n 的指数一定是 1,所以不需要输出 ^ } cout << endl; } int main() { // IO 优化 ios::sync_with_stdio(false); cin.tie(0); solve(); return 0; }
5. 数据生成器 (1.in - 10.in)
为了生成高质量的 级别数据,我们需要组合大质数、完全平方数等边界情况。
/** * B3871 数据生成器 * 生成 1.in/1.out ~ 10.in/10.out */ #include <iostream> #include <fstream> #include <string> #include <vector> #include <cstdlib> #include <ctime> #include <algorithm> #include <cmath> using namespace std; // ========================================== // Part 1: 标准题解逻辑 (用于生成 .out) // ========================================== string solve(long long n) { string ans = ""; bool first = true; for (long long i = 2; i * i <= n; i++) { if (n % i == 0) { int cnt = 0; while (n % i == 0) { cnt++; n /= i; } if (!first) ans += " * "; ans += to_string(i); if (cnt > 1) ans += "^" + to_string(cnt); first = false; } } if (n > 1) { if (!first) ans += " * "; ans += to_string(n); } return ans; } // ========================================== // Part 2: 数据构造工具 // ========================================== // 生成大的随机数 long long randLL() { // rand() 通常只有 15位 (32767),需要拼接 long long r = (long long)rand() << 48 | (long long)rand() << 32 | (long long)rand() << 16 | rand(); return abs(r); // 确保正数 } // 生成范围内的随机数 [min, max] long long randRange(long long min, long long max) { return min + randLL() % (max - min + 1); } // 简单的质数判断,用于构造数据 bool isPrime(long long n) { if (n < 2) return false; for (long long i = 2; i * i <= n; i++) { if (n % i == 0) return false; } return true; } // 获取一个大的质数 long long getPrime(long long min, long long max) { while (true) { long long p = randRange(min, max); if (isPrime(p)) return p; } } // ========================================== // Part 3: 测试点生成 // ========================================== void makeData(int fileId) { string inName = to_string(fileId) + ".in"; string outName = to_string(fileId) + ".out"; ofstream fin(inName); ofstream fout(outName); long long N; if (fileId <= 2) { // 1-2: 小数据,基础测试 N = randRange(2, 100); } else if (fileId == 3) { // 3: 纯大质数 (测试循环是否正确跑到 sqrt) N = getPrime(1e9, 1e11); } else if (fileId == 4) { // 4: 2的幂次 (测试 ^ 输出) // 2^39 < 10^12 N = 1; int p = randRange(30, 39); for(int i=0; i<p; i++) N *= 2; } else if (fileId == 5) { // 5: 完全平方数 (测试 p^2) long long root = getPrime(1000, 10000); N = root * root; } else if (fileId == 6) { // 6: 两个大质数的乘积 (测试收尾 n > 1) long long p1 = getPrime(100000, 200000); long long p2 = getPrime(100000, 200000); N = p1 * p2; } else if (fileId <= 8) { // 7-8: 随机大合数 N = randRange(1e10, 1e12); } else if (fileId == 9) { // 9: 边界测试,非常接近 10^12 的大质数 N = getPrime(1e12 - 10000, 1e12); } else { // 10: 题目最大值 (如果是合数或质数都行,这里直接取10^12) N = 1000000000000LL; } fin << N << endl; fout << solve(N) << endl; fin.close(); fout.close(); cout << "Generated Case " << fileId << ": N=" << N << endl; } int main() { srand((unsigned)time(NULL)); for (int i = 1; i <= 10; i++) { makeData(i); } return 0; }教练总结
这道题是 GESP 五级的分水岭,标志着从“模拟”走向“算法”。
- 时间复杂度意识:如果不理解试除法的 原理,直接写
for (i=2; i<n; i++),在 时程序会跑几千年(超时)。 - 数据类型敏感度:时刻盯着 这个界限。超过它,就要用
long long。 - 细节:处理好末尾的那个质因数(循环结束后的
if (n > 1)),是这道题最容易丢分的地方。
- :
- 1
信息
- ID
- 14213
- 时间
- 1000ms
- 内存
- 32MiB
- 难度
- 3
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者