一种隐式的01背包问题
例题:洛谷P1466[USACO2.2]集合 Subset Sums
题目描述
对于从 1 ∼ n 1\sim n 1∼n 的连续整数集合,能划分成两个子集合,且保证每个集合的数字和是相等的。举个例子,如果 n = 3 n=3 n=3,对于 { 1 , 2 , 3 } \{1,2,3\} {1,2,3} 能划分成两个子集合,每个子集合的所有数字和是相等的:
{
3
}
\{3\}
{3} 和
{
1
,
2
}
\{1,2\}
{1,2} 是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数)
如果
n
=
7
n=7
n=7,有四种方法能划分集合
{
1
,
2
,
3
,
4
,
5
,
6
,
7
}
\{1,2,3,4,5,6,7 \}
{1,2,3,4,5,6,7},每一种分法的子集合各数字和是相等的:
{
1
,
6
,
7
}
\{1,6,7\}
{1,6,7} 和
{
2
,
3
,
4
,
5
}
\{2,3,4,5\}
{2,3,4,5}
{
2
,
5
,
7
}
\{2,5,7\}
{2,5,7} 和
{
1
,
3
,
4
,
6
}
\{1,3,4,6\}
{1,3,4,6}
{
3
,
4
,
7
}
\{3,4,7\}
{3,4,7} 和
{
1
,
2
,
5
,
6
}
\{1,2,5,6\}
{1,2,5,6}
{
1
,
2
,
4
,
7
}
\{1,2,4,7\}
{1,2,4,7} 和
{
3
,
5
,
6
}
\{3,5,6\}
{3,5,6}
给出 n n n,你的程序应该输出划分方案总数。
提示
【数据范围】
对于
100
%
100\%
100% 的数据,
1
≤
n
≤
39
1\le n \le 39
1≤n≤39。
题目分析:
1.首先我们需要判断什么样的集合可以分成两等份。首先需要1~n的和为偶数,1–n的和我们可以使用高斯公式计算。sum = n*(n+1)/2;
若sum为奇数则一定不可能分成两部分直接输出0.
2.若sum为偶数的话,一部分的和为sum/2.则原问题转化为从1~n中选出一部分数字,计算使这部分数字和为sum/2的种类数。
3.从题意中可知,每当选出一部分数和为sum/2的数字,其余剩下部分的和也为sum/2.则我们所求的答案为和为sum/2的数字的种类数除以2.
4.我们假设有X钟方式凑成J,同时Y-Z = J。(1<=Z<=n) .由此我们可以得出结论
dp[Y] = sum(dp[Y-Z])(1<=Z<=n)
又因为集合的无序性 { 1 , 2 } \{1,2\} {1,2} 与 { 2 , 1 } \{2,1\} {2,1} 是同一种集合,为了避免出现这一类的重复计算我们可以以一个有序集合来表示所含元素相同的集合。
5.初始化条件dp[0] = 1,其含义为总和为0的集合有一种及空集。
6.我们注意到n是小于等于39的所以其时间复杂度不高,所以不用担心TLE为了避免int爆了,我们可以直接long long。(不开long long见祖宗
)别问我为什么要写这一点,因为我开int炸了。
代码来咯!!!
#include<bits/stdc++.h>
using namespace std;
const int MAX = 8001;
long long dp[8001];
int main(){
int n;
printf("%d,"&n);
int sum= (n+1)*n/2;
memset(dp,0,sizeof(dp));
dp[0] = 1;
for(int i = 1;i<=n;++i){
for(int j = sum;j>=i;--j){
dp[j] += dp[j-i];
}
}
if(sum%2) scanf("0");
else
cout << scanf("%ld",dp[sum/2]/2);
}