DP·动规·子集和

题目信息

对于由从1到N (1 <= N <= 39)这N个连续的整数组成的集合来说,我们有时可以将集合分成两个部分和相同的子集合。
例如,N=3时,可以将集合{1, 2, 3} 分为{1,2}{3}。此时称有一种方式(即与顺序无关)。
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。

输出

方式数。若不存在这样的拆分,则输出0。

测试样例

7
4

解答

#include <iostream>

using namespace std;
int dp[40][400];

int main()
{
    //freopen("E://test.txt", "r", stdin);
    int N;
    cin >> N;
    int sum = (N + 1) * N / 4;
    if ((N + 1) * N % 4 != 0)
    {
        cout << 0 << endl;
    }
    else
    {
        dp[0][0] = 1;
        for (int i = 1; i <= N; i++)
        {
            for (int j = 0; j <= sum; j++)
            {
                if (j >= i)
                {
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - i];
                }
                else
                {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        cout << dp[N][sum] / 2 << endl;
    }
    return 0;
}

优化的DP

#include <iostream>

using namespace std;
int dp[400];

int main()
{
    //freopen("E://test.txt", "r", stdin);
    int N;
    cin >> N;
    int sum = (N + 1) * N / 4;
    if ((N + 1) * N % 4 != 0)
    {
        cout << 0 << endl;
    }
    else
    {
        dp[0] = 1;
        for (int i = 1; i <= N; i++)
        {
            for (int j = sum; j >= 0; j--)
            {
                dp[j] += dp[j - i];
            }
        }
        cout << dp[sum] / 2 << endl;
    }
    return 0;
}

想法

题目的意思是可以将数组分为两堆,且两堆的和相等。
由于是从1到N求和,所以所有数字的和为(1 + n) * n / 2,那么一边的数字和为再除2:(1 + n) * n / 4,但是如果再除2的时候如果无法除尽的话意味着无法平分,直接输出0就可以了。
给定集合S={1,2,3...,N},我们将其一字排开,挨个判断每个数是否应该加入到满足整数和为N*(N+1)/4的子集合C。对于每个数,要么可以被加入到集合C,要么不可以被加入,只有这两种可能。那么如何知道当前的整数是否应该加入子集合呢?由于这个子集合的和与单个整数大小有悬殊,我们似乎一眼看不出来。既然这样,我们不妨缩小问题规模来渐进考虑,而缩小问题规模的常见切入点是减小“自变量”的规模。
重读“接下来…”那句话,发现其中有两个条件,一个目的。目的是“有多少种选取方案”,这就是因变量。条件是“选出k个数”和“使其和为N(N+1)/4”,这就是两个自变量,一个限制选取的整数,另一个限制选出的整数的和。既然有了自变量和因变量,不如定义个函数出来更好的描述问题:F(i, sum)。在函数F(i, sum)中,i代表当前需要判断集合S中第i个数是否应该加入子集合,sum代表此时限制的子集合整数和大小,F(i, sum)代表限制子集合整数和为sum时,集合{0,1,2...,i-1,i}中可提供的选取方案。
如果我们要减小自变量规模,就要从上面两个自变量下手。先看选取的整数,集合S中的最小整数是1,我们加入一个更小的整数0(显然,0不会影响集合的划分)来辅助思考。对于选出的整数的和,也就是限制的子集合的整数和,我们也从最小的0开始判断,那么问题的最小规模就是:限制子集合的整数和为0时,考虑整数0是否可以加入子集合?这个问题的答案是肯定的,当子集合的和为0时,完全可以将0加入,且仅有次一种方案,那么用上述函数来表达就是:F(0, 0) = 1,明白了此点,也就顺利地得出:F(0, 1)=F(0, 2)=F(0, 3)...=F(0, N)=0,因为限制子集合整数和大于0时,光有0无论如何也不能选出符合此限制的整数集合,即可行方案数为0。
假设此时,S中前i-1个元素都判断完了,紧接着应该判断第i个元素,与此同时,子集合的整数和被限定为sum,那么这第i个元素要不要被加入子集合呢?对此,我们做如下推断:
1:如果这第i个元素本身大于子集合的整数和sum,即i>sum,那么这第i个元素肯定不能加入子集合,否则就超出子集合整数和限制了。此时:F(i, sum)=F(i-1, sum),意思就是在相等的子集合整数和限制下,既然第i个元素没被加入,那么判断完第i个元素后的整数选取方案与判断完第i-1个数时的方案应该是相同的。
2:如果这第i个元素小于子集合整数和,那么就有两种考虑:
2.1:坚持不把第i个元素放入子集合,那么此时整数的选取方案仍然有F(i-1, sum)种。
2.2:如果把第i个元素放入了子集合,那么此时整数的选取方案有F(i-1, sum-i)种,sum-i的含义在于既然要放入第i个元素,就要给它留下足够的空间。F(i-1, sum-i)是在肯定要放入元素i的情形下,放入元素i前,整数的选取方案。也即是说,i<=sum时,F(i, sum)=F(i-1, sum)+F(i-1, sum-i)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhj12399

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值