在解决USACO培训问题时,我发现了动态编程。处理这个概念的第一个训练问题是一个叫做子集和的问题。
问题陈述遵循:
对于从1到N的多组连续整数(1 <= N <= 39),可以将该组分成两组,其总和相同。 例如,如果N = 3,则可以以一种方式对集合{1,2,3}进行分区,以使两个子集的总和相同:
{3}和{1,2}
这计为单个分区(即,将顺序颠倒计为相同的分区,因此不会增加分区的数量)。 如果N = 7,有四种方法可以对集合{1,2,3,... 7}进行分区,以便每个分区具有相同的总和:
{1,6,7}和{2,3,4,5}
{2,5,7}和{1,3,4,6}
{3,4,7}和{1,2,5,6}
{1,2,4,7}和{3,5,6}
给定N,你的程序应该打印包含从1到N的整数的集合的方式的数量可以被分成两个总和相同的集合。如果没有这样的方法,请打印0。 您的程序必须计算答案,而不是从表中查找。
输入格式 输入文件包含一行,其中一个整数表示N,如上所述。
SAMPLE INPUT(文件subset.in) 7
输出格式 输出文件包含一个带有单个整数的行,该整数表示可以从集合{1,2,...,N}中生成多少个相同和的分区。如果没有方法进行相同和分区,则输出文件应包含0。 SAMPLE OUTPUT(文件subset.out) 4
经过多次阅读后,我发现了一种被解释为 0/1背包问题的变体的算法。我在我的代码中实现了它,我解决了这个问题。但是,我不知道我的代码是如何工作的或者是怎么回事。
*主要问题:我想知道是否有人可以向我解释背包算法是如何工作的,以及我的程序如何在我的代码中实现这一点?
我的代码:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream fin("subset.in");
ofstream fout("subset.out");
long long num=0, ways[800]={0};
ways[0]=1;
cin >> num;
if(((num*(num+1))/2)%2 == 1)
{
fout << "0" << endl;
return 0;
}
//THIS IS THE BLOCK OF CODE THAT IS SUPPOSED TO BE DERIVED FROM THE
// O/1 KNAPSACK PROBLEM
for (int i = 1; i <= num; i++)
{
for (int j = (num*(num+1))/2 - i; j >= 0; --j)
{
ways[j + i] += ways[j];
}
}
fout << ways[(num*(num+1))/2/2]/2 << endl;
return 0;
}
*注意:请注意,这段代码确实有用,我只想解释它为什么有效。谢谢:)
答案 0 :(得分:1)
我想知道为什么众多消息来源无法帮助你。
用我丑陋的英语再试一次:
方式[0] = 1;
有一种方法可以赚空钱
NUM *(NUM + 1))/ 2
这是MaxSum - 范围1..num
中所有数字的总和(算术级数之和)
if((num *(num + 1))/ 2)%2 == 1)
没有机会将奇数值分成两个相等的部分
for(int i = 1; i&lt; = num; i ++)
对于范围内的每个数字
可以使用总和for(int j =(num *(num + 1))/ 2 - i; j&gt; = 0; --j) 方式[j + i] + =方式[j];
j + i
和值为j
的项构建总和i
。
例如,考虑你想要和15
在外循环的第一步,你使用的是数字1,并且有ways[14]
种变体可以得到这个总和
在外循环的第二步,您使用数字2,并且有ways[13]
新的变体来计算此总和,您必须添加这些新变体。
在外循环的第三步,您使用数字3,并且有ways[12]
新的变体来计算此总和,您必须添加这些新变体。
方式[(NUM *(NUM + 1))/ 2/2] / 2
输出MaxSum / 2的方法数,除以2以排除对称变量([1,4] + [2,3] / [2,3] + [1,4])
自我思考的问题:为什么内循环反向?