Hrbust 1541集合划分 & Hrbust 2002幂集【dp】

Description
对于从1到N (1 <= N <= 39) 的连续整数集合,能划分成两个子集合,且保证每个集合的数字和是相等的。举个例子,如果N=3,对于{1,2,3}能划分成两个子集合,每个子集合的所有数字和是相等的:
{3} 和 {1,2}
这是唯一一种分法(交换集合位置被认为是同一种划分方案,因此不会增加划分方案总数) 如果N=7,有四种方法能划分集合{1,2,3,4,5,6,7},每一种分法的子集合各数字和是相等的:
{1,6,7} 和 {2,3,4,5} {注 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。
Input
有多组测试数据。

对于每组测试数据,输入一个整数n。

Output
对于每组测试数据,输出划分方案总数,如果不存在则输出0。
Sample Input
7
Sample Output
4

**题解:**要把这些数分成两个求和后相等的集合,那么首先计算求和后的值,然后计算一个集合的求和是多少。

可以发现,这是一个求方案数的动态规划【递推?】
其实就是一个递推。。。。只有求和为0时方案数为1,一个数字0,求和值为j即用1到n每一个数加入到集合中时的方案数之和

诶现在一看真是非常的简单了orz

最后注意dp【half】结果其实是存在重复的,因为集合A是1 2 4 7 ,集合B是3 5 6与集合A是3 5 6 而集合B是1 2 4 7是一模一样的,要除2

#include<stdio.h>
#include<string.h>
int main()
{
    long long dp[600];
    int i,n,j;
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,0,sizeof(dp));
        int sum=n*(n+1)/2;///等差数列前n项求和公式
        int half=sum/2;
        if(sum%2!=0)///因为要把sum总和分成两个相等求和的集合,因此sum总和必须能被2整除否则一个方法也没有
        {
            printf("0\n");
            continue;
        }
        dp[0]=1;///当输入0时肯定只有一种分法
        for(i=1;i<=n;i++)///dp第i个数字
        {
            for(j=half;j>=i;j--)
            {
                dp[j]=dp[j]+dp[j-i];///达到每一个总和的方法,都是减去这个数所继承的上一个数的方法个数
            }
//            for(int k=half;k>=0;k--)
//            {
//                printf("%d\n",dp[k]);
//            }
//            printf("====\n");
        }
        printf("%lld\n",dp[half]/2);///此处算上的是两个集合交换位置,有重复的,要除2
    }
    return 0;
}

幂集
Description
所谓幂集,就是原集合中所有子集构成的集合,例如集合{1,2,3}的幂集为{{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3},{空集}}。现有一个元素个数为n的集合A={a1,a2,a3,a4…an},请求出集合A的幂集中有多少个 子集的和等于整数x。
Input
有多组测试数据处理到文件结束,每组数据第一行包含两个整数n(n<=30)和x(x>0);

第二行有n个整数,0<ai<300,代表集合A中的元素。

Output
输出集合A的幂集中子集和为x的子集的个数。
Sample Input
5 6
1 2 2 4 5
3 7
1 2 3

Sample Output
3
0

Hint
第一组样例中{1,5},{2,4},{2,4}这三个子集的和都等于6,所以答案为3.

**题解:**把这两题放一起因为是一样的,上一题中的背包容量是所有数求和的一半。这题中其实只需要求得集合内所有数能组成求和为多大的背包,即可像上一题一样得到用01背包计算每种求和的方法数,最后输出求和为x时的方法数,即是结果

#include<stdio.h>///和1541的 集合划分 是一样的,把数字当一个物品,然后01背包
#include<string.h>
int main()
{
    int a[39],i,j,x,n,dp[9005];///有30个数,每个数不超过300,dp的单位是数值大小,因此最大值是9000
    while(scanf("%d%d",&n,&x)!=EOF)
    {
        int sum=0;///这里所求的子集数量,其实就是所有元素能加和得到方法数
        for(i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            sum+=a[i];///求的最大dp值
        }
        memset(dp,0,sizeof(dp));///记得清空
        dp[0]=1;///当求和为0时,只有空集是这样的,所以只有1个子集(输入的元素大于0)
        for(i=0;i<n;i++)
        {
            for(j=sum;j>=a[i];j--)
            {
                dp[j]=dp[j]+dp[j-a[i]];
            }
        }
        printf("%d\n",dp[x]);///只需要当总和为x时的方法数
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值