背包DP | 子集和问题

DP

成绩10开启时间2020年03月10日 星期二 07:55
折扣0.8折扣时间2020年04月7日 星期二 23:55
允许迟交关闭时间2020年04月7日 星期二 23:55

对于由从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。

 测试输入期待的输出时间限制内存限制额外进程
测试用例 1 
  1. 7↵
 
  1. 4↵
1秒64M0

我们将集合的元素和的一半记为sum,此题可以转化为求:集合{1..n}中和为 sum 的子集个数的一半。若sum不为整数,显然此题不存在这样的拆分。下面继续讨论存在解的情况:

定义一个 calc(n, sum) 函数:求集合{1..n}中和为sum的子集的个数。(为了防止反复递归、提高效率,将此函数的返回结果保存于数组dp[n][sum]中)

🔺至于函数如何计算,我们可以分两种情况思考:

  • 当sum > n:那么其子集中不可含n,dp[n][sum] = calc(n-1, sum)(选择不含n)  
  • 当sun <= n:那么其子集中可以含n也可以不含n,dp[n][sum] = calc(n-1, sum) (选择不含n)+ calc(n-1, sum-n)(选择含n)

(其实每一层的 calc(n, sum) 函数就是在做选择:选 n或不选 n。很像背包问题:从1..n中选出若干物品放入背包使得背包恰好装满,此时是否将 n 放入背包呢 ?)

🔺递归的终点

1、当递归到 n = 0 时:

  • 若sum = 0,说明此路选择恰好将 "背包填满"(选择出的子集和恰为最初的sum),算一个合理的结果,应该返回1
  • 若sum > 0,说明此路选择 "背包没有满" (选出的子集和不够最初的sum),结果不合理,应返回0

2、当此次调用的calc(n,sum)已经被调用过:结果值已经存储在dp[n][sum]中,直接返回dp[n][sum]


注意:dp数组的初始化问题。

完整AC代码:

//
// Created by LittleCat on 2020/3/19.
//
#include <cstdio>
#include <cstring>

#define maxX 40
#define maxY 391
int dp[maxX][maxY];

/* 求集合[1..n]中和为sum的子集的个数 */
int calc(int n, int sum) {
    if (dp[n][sum] != -1)   //递归的终点: n==0 or 我们已经计算过了
        return dp[n][sum];
    /* 直接计算,将结果储存在dp数组中 */
    else if (n > sum)
        dp[n][sum] = calc(n - 1, sum);
    else
        dp[n][sum] = calc(n - 1, sum) + calc(n - 1, sum - n);
    return dp[n][sum];  //返回计算的结果
}

int main() {
    int n, sum;
    scanf("%d", &n);

    /* 没有这样子的子集合 */
    if ((n + 1) * n % 4 != 0) {
        printf("0\n");
        return 0;
    }

    /* 设置dp数组的初始情况 */
    memset(dp, -1, sizeof(dp));  //数组初始化为-1
    dp[0][0] = 1;  //集合{0}中和为0的子集的个数为1
    for (int j = 1; j < maxY; j++)
        dp[0][j] = 0; //集合{0}中和大于0的子集的个数为0

    sum = (n + 1) * n / 4;  //子集合需要满足的部分和
    printf("%d\n", calc(n, sum) / 2);
}

有任何问题欢迎评论交流,如果本文对您有帮助不妨点点赞,嘻嘻~  



欢迎关注个人公众号“ 鸡翅编程 ”,这里是认真且乖巧的码农一枚。

---- 做最乖巧的博客er,做最扎实的程序员 ----

旨在用心写好每一篇文章,平常会把笔记汇总成推送更新~

在这里插入图片描述

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值