【问题标题】:Combinatorics - The Candies组合数学 - 糖果
【发布时间】:2015-10-22 21:53:11
【问题描述】:

我在比赛的某个地方发现了这个问题,但还没有提出解决方案。

这个男孩有苹果,放在盒子里。一盒不超过N/2。 他能用多少种方法把糖果装进盒子里。

所以我想做的是使用 DP 实现解决方案。这是我的代码:

#include <iostream>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <unistd.h>
#include <vector>
#define size 1002
using namespace std;

long long a[size][size];
int n, k;
int main()
{
    cin >> n >> k;
    int kk = n/2;

    for(int i = 0; i <= k; ++i)
        a[0][i] = 1;
    a[0][0] = 0;

    for(int i = 0; i <= kk; ++i)
        a[i][1] = 1;

    for(int i = 1; i <= n; ++i) {

        for(int j = 2; j <= k; ++j) {
            int index = 0;
            long long res = 0;
            while(1) {
                res += a[i-index][j - 1];
                index += 1;

                if(index == kk + 1 || i-index < 0)
                    break;
            }
            a[i][j] = res;

        }
    }
    cout << a[n][k] << endl;

}

但问题是我们有大量的输入,例如:

2 ≤ N ≤ 1000 是糖果的数量,N - 偶数; 2 ≤ S ≤ 1000 - 是小盒子的数量。

因此,对于像 N = 1000 和 S = 1000 这样的输入,我必须花费 5*10^8 次操作。而且数字很大,所以我必须使用 BigInteger 算术?

也许有算法可以在线性时间内解决问题?感谢并为我的英语感到抱歉!

【问题讨论】:

  • 您的代码对于较小的输入是否给出了正确的结果?
  • @tobi303 是的,该解决方案适用于较小的输入。
  • 看看this article 的最后一行,它给出了一个封闭的公式来计算数字(将(k,m,R)替换为(N,@ 987654327@,(N/2)), resp.)。
  • 我猜“在一个小盒子里放的糖果不超过 N/2”应该是“没有小盒子里应该有超过 N/2 个糖果”?

标签: c++ algorithm dynamic-programming combinatorics


【解决方案1】:

您可以通过以下观察轻松地将时间复杂度从 O(kn^2) 降低到 O(nk):

for(int i = 1; i <= n; ++i) {

    for(int j = 2; j <= k; ++j) {
        int index = 0;
        long long res = 0;
        while(1) {
            res += a[i-index][j - 1];
            index += 1;

            if(index == kk + 1 || i-index < 0)
                break;
        }
        a[i][j] = res;

    }
}

对于每个a[i][j],我们可以很容易地看到

a[i][j] = sum a[k][j - 1] 与 k 从 (i - n/2)i

所以,如果我们创建一个数组sum 来存储上一步所有索引的总和,我们可以从上面的嵌套循环中减少一个for循环

a[i][j] = sum[i] - sum[i - (n/2) - 1];

伪代码:

long long sum[n + 1];
for(int j = 2; j <= k; ++j) {
    long long nxt[n + 1];
    for(int i = 1; i <= n; ++i) {
        int index = 0;
        long long res = sum[i] - sum[i - (n/2) - 1];

        a[i][j] = res;
        nxt[i] = nxt[i - 1] + a[i][j];//Prepare the sum array for next step

    }
    sum = nxt;
}

注意:上面这段代码没有处理数组sum的初始化步骤,也没有处理i

更新:

我下面的 Java 解决方案通过使用类似的想法被接受:

public static void main(String[] args) throws FileNotFoundException {
    // PrintWriter out = new PrintWriter(new FileOutputStream(new File(
    // "output.txt")));
    PrintWriter out = new PrintWriter(System.out);
    Scanner in = new Scanner();
    int n = in.nextInt();
    int s = in.nextInt();
    BigInteger[][] dp = new BigInteger[n + 1][2];
    BigInteger[][] count = new BigInteger[2][n + 1];
    int cur = 1;
    for (int i = 0; i <= n / 2; i++) {
        dp[i][0] = BigInteger.ONE;
        count[0][i] = (i > 0 ? count[0][i - 1]  : BigInteger.ZERO)
                .add(dp[i][0]);

    }
    for (int i = n / 2 + 1; i <= n; i++) {
        dp[i][0] = BigInteger.ZERO;
        count[0][i] = count[0][i - 1];
    }
    for (int i = 2; i <= s; i++) {
        for (int j = 0; j <= n; j++) {

            dp[j][cur] = dp[j][1 - cur].add((j > 0 ? count[1 - cur][j - 1]
                    : BigInteger.ZERO)
                    .subtract(j > n / 2 ? count[1 - cur][j - (n / 2) - 1]
                            : BigInteger.ZERO));

            count[cur][j] = (j > 0  ? count[cur][j - 1] : BigInteger.ZERO)
                    .add(dp[j][cur]);
        }
        cur = 1 - cur;
    }
    out.println(dp[n][1 - cur]);
    out.close();
}

【讨论】:

  • 数字的大小呢?如果我使用 BigIntegers 算术时间复杂度会再次增加。
  • @James BigInteger 没问题,因为我们只需要加/减,所以时间复杂度不会显着增加。我已经提交并通过实施具有类似想法的 Java 解决方案被接受。
猜你喜欢
  • 2011-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多