组合学 - 糖果
Combinatorics - The Candies
我在比赛的某个地方发现了这个问题,但还没能想出解决办法。
The boy has apples and keeps in boxes. In one box no more than N/2.
How many methods he can put candies to boxes.
所以我想做的是使用 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 is a quantity of the candies, N - even; 2 ≤ S ≤ 1000 - is a quantity of small boxes.
因此,对于 N = 1000 和 S = 1000 这样的输入,我必须花费 5*10^8 次操作。而且数字很大,所以我必须使用BigInteger算法?
也许有算法可以在线性时间内解决问题?谢谢,对不起我的英语!
您可以通过以下观察轻松地将时间复杂度从 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 < n/2的情况。这些情况应该很容易处理。
更新:
我的以下 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();
}
我在比赛的某个地方发现了这个问题,但还没能想出解决办法。
The boy has apples and keeps in boxes. In one box no more than N/2. How many methods he can put candies to boxes.
所以我想做的是使用 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 is a quantity of the candies, N - even; 2 ≤ S ≤ 1000 - is a quantity of small boxes.
因此,对于 N = 1000 和 S = 1000 这样的输入,我必须花费 5*10^8 次操作。而且数字很大,所以我必须使用BigInteger算法?
也许有算法可以在线性时间内解决问题?谢谢,对不起我的英语!
您可以通过以下观察轻松地将时间复杂度从 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 < n/2的情况。这些情况应该很容易处理。
更新:
我的以下 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();
}