三数之和升级版!K-P 分解问题

133 阅读6分钟

K-P 分解

问题背景

在数论中,我们研究如何将一个正整数 N 分解为其他整数的幂和。本问题关注一种特定的分解方式,称为 K-P 分解

形式化定义

一个正整数 N 的 K-P 分解,是指找到 K 个正整数 n_1, n_2, ..., n_K,使得它们满足以下等式:

N=n1P+n2P++nKPN = n_1^P + n_2^P + \dots + n_K^P

其中:

  • N, K, P 是给定的正整数。
  • n_1, n_2, ..., n_K 被称为分解因子

最优解筛选规则

对于一个给定的 N, K, P,可能存在多种不同的分解方案(即多组不同的分解因子)。我们需要在所有可能的解中,根据以下规则选出唯一的最优解:

  1. 规则一(因子和最大): 优先选择那个分解因子之和 (n_1 + n_2 + ... + n_K) 最大的解。

  2. 规则二(序列最大): 如果在满足规则一的前提下,仍然有多个解(它们的因子和相等且最大),则选择那个分解因子序列在字典序上最大的解。

    • 序列比较方式: 将每个解的因子序列按降序排列,然后从左到右逐个比较数字。

任务要求

给定正整数 nValue, kValue, pValue,请找出其 K-P 分解的唯一最优解。

  • 如果找到解,则返回其分解因子序列(按降序排列)。
  • 如果无解,则返回一个空序列 []

输入格式

  • nValue: 第一个参数,即待分解的正整数 N
    • 1 <= nValue <= 400
  • kValue: 第二个参数,即分解因子的数量 K
    • 1 <= kValue <= nValue
  • pValue: 第三个参数,即次幂 P
    • 2 <= pValue <= 7

输出格式

  • 一个整数序列(列表或数组),代表最优解的分解因子(已降序排列);若无解,则为空序列。

样例说明

样例 1

  • 输入:

    • nValue = 169, kValue = 5, pValue = 2
  • 输出: [6, 6, 6, 6, 5]

  • 解释:

    1. 寻找所有解: 169 的 5-2 分解(将169写成5个正整数的平方和)存在多个解,例如:
      • 解 A: 122+42+22+22+12=144+16+4+4+1=16912^2 + 4^2 + 2^2 + 2^2 + 1^2 = 144+16+4+4+1 = 169
      • 解 B: 112+62+22+22+22=121+36+4+4+4=16911^2 + 6^2 + 2^2 + 2^2 + 2^2 = 121+36+4+4+4 = 169
      • 解 C: 62+62+62+62+52=36+36+36+36+25=1696^2 + 6^2 + 6^2 + 6^2 + 5^2 = 36+36+36+36+25 = 169
    2. 应用规则一 (因子和最大): 计算各解的因子和。
      • 解 A 的和: 12+4+2+2+1 = 21
      • 解 B 的和: 11+6+2+2+2 = 23
      • 解 C 的和: 6+6+6+6+5 = 29
      • ...(其他解的和可能更小)
      • 因子和最大的解是 C,其和为 29。假设这是唯一的最大和解。
    3. 最终结果: 输出解 C 的因子序列,降序排列后为 [6, 6, 6, 6, 5]

样例 2

  • 输入:

    • nValue = 169, kValue = 167, pValue = 3
  • 输出: []

  • 解释: 我们需要将 169 分解为 167 个正整数的立方和。

    • 所能构成的最小和是所有因子都取 113×167=1671^3 \times 167 = 167
    • 任何其他组合都会使和大于 167(例如,将一个1换成2,和会增加 2313=72^3 - 1^3 = 7)。
    • 因此,不可能凑出 169 这个和。无解,返回空序列。

样例 3

  • 输入:

    • nValue = 28, kValue = 4, pValue = 2
  • 输出: [4, 2, 2, 2]

  • 解释:

    1. 寻找所有解: 28 的 4-2 分解,存在多个解。
    2. 应用规则一 (因子和最大): 经过搜索发现,因子和最大的解有两个:
      • 解 A: 42+22+22+22=16+4+4+4=284^2+2^2+2^2+2^2 = 16+4+4+4=28。因子序列 [4,2,2,2],和为 4+2+2+2 = 10
      • 解 B: 32+32+32+12=9+9+9+1=283^2+3^2+3^2+1^2 = 9+9+9+1=28。因子序列 [3,3,3,1],和为 3+3+3+1 = 10
    3. 应用规则二 (序列最大): 比较这两个和同为 10 的解的序列(它们已经是降序)。
      • 比较 [4, 2, 2, 2][3, 3, 3, 1]
      • 从第一位开始比较,4 > 3。因此,序列 [4, 2, 2, 2] 更大。
    4. 最终结果: 选择序列 [4, 2, 2, 2]
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 解决 K-P 分解问题的方案类。
 * 核心算法:带有剪枝的深度优先搜索 (DFS)。
 */
public class KPSolution {

    // --- 用于存储最终结果的成员变量 ---
    // 存储最优解的分解因子序列
    private List<Integer> bestSolution = new ArrayList<>();
    // 存储最优解的因子之和,初始化为-1表示暂未找到解
    private int maxFactorSum = -1;

    // --- DFS过程中共享的参数 ---
    private int nValue, kValue, pValue;
    // 预计算i^P的值,避免重复计算
    private List<Integer> powers = new ArrayList<>();

    /**
     * 主解决函数
     *
     * @param n K-P分解的目标值N
     * @param k K-P分解的因子数量K
     * @param p K-P分解的次幂P
     * @return 最优解的分解因子序列(降序),若无解则返回空列表
     */
    public List<Integer> solve(int n, int k, int p) {
        this.nValue = n;
        this.kValue = k;
        this.pValue = p;

        // 预计算所有可能用到的 i^p 的值
        int i = 0;
        while (true) {
            int power = (int) Math.pow(i, p);
            if (power > nValue) {
                break;
            }
            powers.add(power);
            i++;
        }

        // 启动DFS搜索。
        // tempSolution: 当前正在构建的解
        // lastFactorIndex: 上一个选择的因子,用于保证因子降序,避免重复组合
        dfs(nValue, kValue, 0, new ArrayList<>(), powers.size() - 1);

        return bestSolution;
    }

    /**
     * 深度优先搜索 (DFS) 辅助方法
     *
     * @param remainingN      剩余需要凑出的值
     * @param remainingK      剩余需要选择的因子数量
     * @param currentFactorSum 当前已选因子的和
     * @param tempSolution    当前正在构建的临时解
     * @param lastFactorIndex 上一个选择的因子在powers数组中的索引(用于保证降序)
     */
    private void dfs(int remainingN, int remainingK, int currentFactorSum,
                     List<Integer> tempSolution, int lastFactorIndex) {

        // --- Base Case: 成功找到一个解 ---
        if (remainingN == 0 && remainingK == 0) {
            // 如果当前解的因子和 > 已记录的最大和,则更新最优解
            if (currentFactorSum > maxFactorSum) {
                maxFactorSum = currentFactorSum;
                bestSolution = new ArrayList<>(tempSolution); // 复制一份作为新的最优解
            }
            // 如果因子和相等,则按字典序比较
            else if (currentFactorSum == maxFactorSum) {
                if (isLexicographicallyLarger(tempSolution, bestSolution)) {
                    bestSolution = new ArrayList<>(tempSolution);
                }
            }
            return; // 结束当前路径的搜索
        }

        // --- 剪枝:无法构成解的失败情况 ---
        // 如果剩余目标值<0 或 剩余因子数<=0,说明此路不通
        if (remainingN < 0 || remainingK <= 0) {
            return;
        }
        // 如果当前搜索的最大因子索引已经小于1(因子必须是正整数)
        if (lastFactorIndex < 1) {
            return;
        }

        // --- 递归搜索 ---
        // 从上一个因子开始,向前(向小)尝试所有可能的因子
        for (int i = lastFactorIndex; i >= 1; i--) {
            // 更多剪枝:如果用当前因子i凑满剩余的k个位置,总和还小于N,说明i太小了,可以直接跳过更小的
            if (remainingN < remainingK * powers.get(i)) {
                 break; // continue 也可以,但因为i是递减的,后续更不可能,可以直接break
            }
            
            // 做出选择
            tempSolution.add(i);

            // 进入下一层递归
            dfs(remainingN - powers.get(i),
                remainingK - 1,
                currentFactorSum + i,
                tempSolution,
                i); // 下一个因子不能超过当前因子 i

            // 撤销选择(回溯),以便尝试其他可能性
            tempSolution.remove(tempSolution.size() - 1);
        }
    }

    /**
     * 比较两个序列的字典序大小
     */
    private boolean isLexicographicallyLarger(List<Integer> list1, List<Integer> list2) {
        for (int i = 0; i < list1.size(); i++) {
            if (!list1.get(i).equals(list2.get(i))) {
                return list1.get(i) > list2.get(i);
            }
        }
        return false; // 如果完全相同,则不算更大
    }

    // --- 用于测试的 main 方法 ---
    public static void main(String[] args) {
        KPSolution solution1 = new KPSolution();
        System.out.println("Case 1: " + solution1.solve(169, 5, 2)); // 期望输出: [6, 6, 6, 6, 5]

        KPSolution solution2 = new KPSolution();
        System.out.println("Case 2: " + solution2.solve(169, 167, 3)); // 期望输出: []

        KPSolution solution3 = new KPSolution();
        System.out.println("Case 3: " + solution3.solve(28, 4, 2)); // 期望输出: [4, 2, 2, 2]
    }
}