ksum,找k数之和等于t

是二维费用背包问题

int kSum(vector<int> &A, int K, int target) {
    // 设dp[i][k][t]表示前i个数中选k个数,它们的和为t的方式数
    // dp[i][k][t] = dp[i-1][k][t] /*不选num*/ + dp[i-1][k-1][t-num] /*选num*/
    // 二维费用的01背包问题;可省掉i这维,i仍从左往右遍历,k和t逆序遍历
    const int N = A.size();
    vector<vector<int>> dp(K + 1, vector<int>(target + 1, 0));
    dp[0][0] = 1;

    for (int num : A) {
        for (int k = K; k >= 1; k--) {
            for (int t = target; t >= num; t--) {
                dp[k][t] += dp[k-1][t-num];
            }
        }
    }
    return dp[K][target];
}

分成“均值相等”的两个子集

bool splitArraySameAverage(vector<int>& A) {
    // 把A分成B和C,不妨设B是较小的那个(1<=k<=N/2)。B和C的均值相等,等于A的整体均值。
    // avgB==avgA, sumB/k==sum/N, sumB==k*sum/N。因为sumB是整数,所以k需满足(k*sum)%N==0
    // 转变成子集和问题:从A中找k个数,k满足(k*sum)%N==0,且k个数的子集和为k*sum/N

    /*
    // 子集和问题是01背包问题
    // 设dp[i][k][v]表示能从前i个数中取k个数,使子集和等于v。
    // 考虑num,dp[i][k][v] = dp[i-1][k][v] || dp[i-1][k-1][v-num]
    // 递推式在i这维只依赖于i-1项,可省掉i这一维。
    // 01背包问题,逆序遍历k和v:dp[k][v] = dp[k][v] || dp[k-1][v-num]

    const int N = A.size();
    int sum = 0;
    for (int num : A) sum += num;
    vector<vector<bool>> dp(N / 2 + 1, vector<bool>(sum + 1, false));
    dp[0][0] = true;
    for (int num : A) { 
        for (int k = N / 2; k >= 1; k--) { // 01背包,逆序遍历k和v
            for (int v = sum; v >= num; v--) {
                dp[k][v] = dp[k][v] || dp[k-1][v-num];
            }
        }
    }

    for (int k = 1; k <= N / 2; k++) {
        if (k * sum % N == 0 && dp[k][k * sum / N]) return true;
    }
    return false;
    */
    
    // 优化:这里num在范围[0,10000],和值v较大,用数组dp[i][k][v]浪费空间,运行超时。
    // 改用sums[i][k]表示从前i个数中取k个数、能达到的和值集,初始sums[0][0] = {0}
    // sums[i][k] = sums[i-1][k] /*不取A[i]*/ ∪ (sums[i-1][k-1] + A[i]) /*取A[i]*/
    // 递推式在i这维只依赖i-1项,可省掉i这维。01背包问题,逆序遍历k
    // sums[k] = sums[k] ∪ (sums[k-1] + A[i])
    
    const int N = A.size();
    int sum = 0;
    for (int num : A) sum += num;
    if (!canSplit(sum, N)) return false; // 提早返回
    
    vector<unordered_set<int>> sums(N / 2 + 1);
    sums[0] = {0};
    for (int num : A) {
        for (int k = N / 2; k >= 1; k--) { // 01背包,逆序遍历
            for (int s : sums[k-1]) {
                sums[k].insert(s + num);
            }
        }
    }
    
    for (int k = 1; k <= N / 2; k++) {
        if (k * sum % N == 0 && sums[k].count(k * sum / N)) return true;
    }
    return false;
}

bool canSplit(int sum, int N) {
    for (int k = 1; k <= N / 2; k++) {
        if (k * sum % N == 0) return true;
    }
    return false;
}