UVa 10447 Sum-up the Primes (II)

题目描述

本题要求将给定的整数 NNN(0≤N≤8000 \leq N \leq 8000≤N≤800)表示为 ttt(t≤12t \leq 12t≤12)个素数之和,且这些素数必须小于 300300300。题目还给出了以下限制条件:

  1. 每个奇数素数 pip_ipi 最多可以使用 fif_ifi 次(0<fi<50 < f_i < 50<fi<5),而素数 222 最多只能使用一次。
  2. 当存在多个解时,输出字典序最小的解。
  3. 如果无法表示,则输出 No Solution.

输入包含多个数据块,每个数据块首先给出 616161 个奇数素数的使用频率限制,接着是多个查询,每个查询包含 NNN、ttt 和一个布尔标志 flagflagflag。当 flagflagflag 为 111 时,允许使用素数 222,否则不允许。

题目分析

关键点

  1. 素数范围 :所有使用的素数必须小于 300300300,总共有 626262 个素数(包括 222 和 616161 个奇数素数)。
  2. 频率限制 :每个奇数素数有特定的使用次数限制,素数 222 最多使用一次。
  3. 字典序最小 :输出解时,需要按字典序排列素数。注意,这里的字典序是基于字符串比较的,例如 101101101 在字典序中比 111111 小。
  4. 奇偶性约束 :若不能使用 222,则剩余素数个数与剩余和的奇偶性必须匹配,因为奇数个奇素数之和为奇数,偶数个奇素数之和为偶数。

解题思路

  1. 预处理素数列表 :将所有小于 300300300 的素数按字典序排序,便于在搜索时直接按字典序遍历。
  2. 深度优先搜索(DFS\texttt{DFS}DFS):从字典序最小的素数开始尝试,逐步构建解。
  3. 剪枝策略
    • 使用频率限制:确保每个素数使用次数不超过限制。
    • 奇偶性检查:若不能使用 222,检查剩余素数个数与剩余和的奇偶性是否匹配。
    • 提前终止:当剩余和小于 000 或已选素数个数超过 ttt 时终止搜索。
  4. 输出处理:找到第一个解即为字典序最小的解,直接输出并终止搜索。

参考代码

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 的使用和奇偶性约束,确保算法高效运行。

相关推荐
寂静山林5 分钟前
UVa 1030 Image Is Everything
算法
AI柠檬11 分钟前
几种排序算法的实现和性能比较
数据结构·算法·c#·排序算法
weixin_4296302640 分钟前
第6章 支持向量机
算法·机器学习·支持向量机
SweetCode40 分钟前
C++ 实现大数加法
开发语言·c++·算法
王哈哈^_^41 分钟前
【数据集】【YOLO】【目标检测】共享单车数据集,共享单车识别数据集 3596 张,YOLO自行车识别算法实战训推教程。
人工智能·算法·yolo·目标检测·计算机视觉·视觉检测·毕业设计
CodeWizard~2 小时前
AtCoder Beginner Contest 430赛后补题
c++·算法·图论
大大dxy大大2 小时前
机器学习-KNN算法示例
人工智能·算法·机器学习
关于不上作者榜就原神启动那件事3 小时前
模拟算法乒乓球
开发语言·c++·算法
Bug退退退1233 小时前
ArrayList 与 LinkedList 的区别
java·数据结构·算法
88号技师3 小时前
2025年7月一区SCI优化算法-Logistic-Gauss Circle optimizer-附Matlab免费代码
开发语言·算法·数学建模·matlab·优化算法