题目描述
本题要求将给定的整数 NNN(0≤N≤8000 \leq N \leq 8000≤N≤800)表示为 ttt(t≤12t \leq 12t≤12)个素数之和,且这些素数必须小于 300300300。题目还给出了以下限制条件:
- 每个奇数素数 pip_ipi 最多可以使用 fif_ifi 次(0<fi<50 < f_i < 50<fi<5),而素数 222 最多只能使用一次。
- 当存在多个解时,输出字典序最小的解。
- 如果无法表示,则输出
No Solution.
。
输入包含多个数据块,每个数据块首先给出 616161 个奇数素数的使用频率限制,接着是多个查询,每个查询包含 NNN、ttt 和一个布尔标志 flagflagflag。当 flagflagflag 为 111 时,允许使用素数 222,否则不允许。
题目分析
关键点
- 素数范围 :所有使用的素数必须小于 300300300,总共有 626262 个素数(包括 222 和 616161 个奇数素数)。
- 频率限制 :每个奇数素数有特定的使用次数限制,素数 222 最多使用一次。
- 字典序最小 :输出解时,需要按字典序排列素数。注意,这里的字典序是基于字符串比较的,例如 101101101 在字典序中比 111111 小。
- 奇偶性约束 :若不能使用 222,则剩余素数个数与剩余和的奇偶性必须匹配,因为奇数个奇素数之和为奇数,偶数个奇素数之和为偶数。
解题思路
- 预处理素数列表 :将所有小于 300300300 的素数按字典序排序,便于在搜索时直接按字典序遍历。
- 深度优先搜索(DFS\texttt{DFS}DFS):从字典序最小的素数开始尝试,逐步构建解。
- 剪枝策略 :
- 使用频率限制:确保每个素数使用次数不超过限制。
- 奇偶性检查:若不能使用 222,检查剩余素数个数与剩余和的奇偶性是否匹配。
- 提前终止:当剩余和小于 000 或已选素数个数超过 ttt 时终止搜索。
- 输出处理:找到第一个解即为字典序最小的解,直接输出并终止搜索。
参考代码
cpp
// Sum-up the Primes (II)
// UVa ID: 10447
// Verdict: Accepted
// Submission Date: 2025-10-14
// UVa Run Time: 0.290s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 素数数组,按字符串字典序排序,便于输出字典序最小的解
int primes[] = {
101, 103, 107, 109, 11, 113, 127, 13, 131, 137, 139, 149,
151, 157, 163, 167, 17, 173, 179, 181, 19, 191, 193, 197,
199, 2, 211, 223, 227, 229, 23, 233, 239, 241, 251, 257,
263, 269, 271, 277, 281, 283, 29, 293, 3, 31, 37, 41, 43,
47, 5, 53, 59, 61, 67, 7, 71, 73, 79, 83, 89, 97
};
// 输入频率限制对应的素数顺序(61 个奇数素数)
int primeOrder[] = {
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53,
59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109,
113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179,
181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241,
251, 257, 263, 269, 271, 277, 281, 283, 293
};
int frequencyLimit[62]; // 每个素数的使用频率限制
int usageCount[62]; // 记录每个素数当前已使用的次数
int solution[14]; // 存储当前找到的解
int primeTwoIndex; // 素数 2 在 primes 数组中的索引位置
int targetSum; // 目标和 N
int primeCount; // 需要使用的素数个数 t
// 深度优先搜索寻找解
bool dfs(int depth, int remainingSum, int startIndex) {
// 已选择足够数量的素数
if (depth == primeCount) {
if (remainingSum == 0) {
// 输出找到的解
cout << solution[0];
for (int i = 1; i < depth; i++) {
cout << "+" << solution[i];
}
cout << "\n";
return true;
}
return false;
}
// 关键剪枝:如果不能使用素数 2,检查剩余和的奇偶性是否与剩余素数个数的奇偶性匹配
// 因为奇数个奇素数的和为奇数,偶数个奇素数的和为偶数
if (frequencyLimit[primeTwoIndex] == 0 && (primeCount - depth) % 2 != remainingSum % 2) return false;
// 遍历所有可用的素数
for (int primeIndex = startIndex; primeIndex < 62; primeIndex++) {
// 检查当前素数是否超过使用频率限制
if (usageCount[primeIndex] >= frequencyLimit[primeIndex]) continue;
int currentPrime = primes[primeIndex];
// 特殊处理素数 2
if (currentPrime == 2) {
// 如果使用 2 会导致奇偶性矛盾,跳过
if (remainingSum % 2 == (primeCount - depth) % 2) continue;
// 必须使用 2 的情况
solution[depth] = 2;
usageCount[primeIndex]++;
if (remainingSum >= 2 && dfs(depth + 1, remainingSum - 2, primeIndex + 1)) return true;
usageCount[primeIndex]--;
return false; // 2 是唯一选择,如果失败则直接返回
}
// 处理普通奇数素数
solution[depth] = currentPrime;
usageCount[primeIndex]++;
if (remainingSum - currentPrime >= 0 && dfs(depth + 1, remainingSum - currentPrime, primeIndex)) return true;
usageCount[primeIndex]--;
}
return false;
}
int main(int argc, char* argv[]) {
cin.tie(0), cout.tie(0), ios::sync_with_stdio(false);
int blockCount; // 数据块数量 B
int queryCount; // 每个块的查询数量 Q
cin >> blockCount;
for (int currentBlock = 1; currentBlock <= blockCount; currentBlock++) {
cout << "Block " << currentBlock << ":\n";
// 读取61个奇数素数的频率限制
for (int i = 0; i < 61; i++) {
// 找到素数 p[i] 在 primes 数组中的位置 k
int k = find(primes, primes + 62, primeOrder[i]) - primes;
cin >> frequencyLimit[k];
}
// 找到素数 2 在 primes 数组中的位置
for (primeTwoIndex = 0; primes[primeTwoIndex] != 2; primeTwoIndex++);
cin >> queryCount;
for (int currentQuery = 1; currentQuery <= queryCount; currentQuery++) {
cout << "Query " << currentQuery << ":\n";
// 读取查询参数:目标和N,素数个数 t,是否能使用 2
cin >> targetSum >> primeCount >> frequencyLimit[primeTwoIndex];
// 重置使用计数
memset(usageCount, 0, sizeof(usageCount));
// 搜索解
if (!dfs(0, targetSum, 0)) cout << "No Solution.\n";
}
cout << "\n";
}
return 0;
}
算法复杂度分析
- 时间复杂度 :最坏情况下需要遍历所有可能的素数组合,但由于 t≤12t \leq 12t≤12 且素数使用频率有限,实际运行时间在合理范围内。
- 空间复杂度 :主要使用固定大小的数组存储素数和状态,空间复杂度为 O(1)O(1)O(1)。
总结
本题通过深度优先搜索结合多种剪枝策略,有效减少了搜索空间,能够在规定时间内找到字典序最小的解。关键点在于合理处理素数 222 的使用和奇偶性约束,确保算法高效运行。