• 算法设计与分析——划分问题(动态规划)


    Description


    给定一个正整数的集合A={a1,a2,….,an},是否可以将其分割成两个子集合,使两个子集合的数加起来的和相等。例A = { 1, 3, 8, 4, 10} 可以分割:{1, 8, 4} 及 {3, 10},

    Input

    第一行集合元素个数n  n <=300 第二行n个整数
     
    Output

    如果能划分成两个集合,输出任意一个子集,否则输出“no”

    Sample Input

    5
    1 3 8 4 10

    Sample Output

    3 10

    这道题是本校OJ上的一道题,作为算法设计与分析课程的作业,要求使用动态规划,我最开始的想法是将这个问题往背包问题上转换,思路应该是正确的,不过一开始没能实现,最终还是写出来了,但这里要介绍一种老师讲的方法。

    用数组a[i]来存储正整数的集合。

    构造一个二维数组表t[i][j],表示{a1,a2......ai}存在子集和为j,并将其值赋为真。

    这句话该怎么理解呢?

    比如此时ai={3,4,8,1,10}

    a1=3,a2=4,a3=8,a4=1,a5=10

    那么{a1,a2,a3}构成的子集有{∅}、{a1}、{a2}、{a3}、{a1,a2}、{a2,a3}、{a1,a3}、{a1,a2,a3},对应的子集和为0,3,4,8,7,12,11,15。

    这样就可以得到t[3][0]=1 t[3][3]=1 t[3][4]=1 t[3][8]=1 t[3][7]=1 t[3][12]=1 t[3][11]=1 t[3][15]=1

    看到这里是不是就应该明白这个二维数组的含义了吧,我们只要求得t[n][sum/2]=1,说明{a1,a2......an}存在一组子集正数其和为sum/2,(这里的sum表示整个整数集合之和),也就是说存在两个子集合加起来的和相等。

    既然是动态规划我们需要探究一下其递归方程应该如何表示

    如果t[i-1][j]=1       那么一定有t[i][j]=1  因为前i-1个正整数的子集能够组成的和为j,那么再加上第i个正整数,还是用之前的子集,能够组成和还是j。

    如果t[i-1][j-a[i]]=1  那么也会有t[i][j]=1  因为前i-1个正整数的子集能够组成的和为j-a[i],那么再加上第i个正整数,还是用之前的子集再加上a[i],能够组成和j。

    由此我们得到了递归迭代方程:

    t[1][0] =1

    t[1][a[1]] =1

    t[i][j] =1 《==( t[i-1][j]=1 || t[i-1][j-a[i]]=1 )

    还是以上面的ai={3,4,8,1,10}为例子,写一下t[i][j]的二维数组表

    我们根据t[n][sum/2]的值就能判断是否存在两个相等的子集和,但怎么样确定子集呢?

    确定子集的方法有很多,OJ后台只需要一种答案即可,这里讲一下我使用的方法。

    我的想法就是观察上面的表看看sum/2是有那些元素贡献的,其实上面的表是从左上角到右下角打印的,我们从后向前逆推组成元素时就需要从右下角向左上角推理,发现t[4][13]是13列第一个带有真值的,最终结果t[5][13]也为真,说明a[4]肯定是纳入子集中,sum/2-a[4]后,也就是13-1=12,我们再来看t[3][12]是12列第一个带有真值的,说明a[3]肯定被纳入子集中,12-a[3],也就是12-8=4;我们再来看t[2][4]是第4列第一个带有真值的,说明a[2]肯定被纳入子集中,4-a[2],也就是4-4=0,至此子集中所有的元素都找到了。为1、8 、4

     

    之前有同学不明白第一张表中的数据是怎么来的,这里我再画一一张表,追踪一下动态规划的过程。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    
    using namespace std;
    int main()
    {
        int n;
        int sum=0;
        int a[310];
        int ans[155];
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            sum+=a[i];
        }
        if(sum%2!=0)
        {
            printf("no
    ");
            return 0;
        }
        int m=sum/2;
        int dp[n+1][m+1];
        memset(dp,0,sizeof(dp));
        dp[1][0]=1;
        dp[1][a[1]]=1;
        for(int i=2; i<=n; i++) //前i个元素
        {
            for(int j=0; j<=m; j++) //可以构成j
            {
                if(dp[i-1][j]||dp[i-1][j-a[i]])
                {
                    dp[i][j]=1;
                }
            }
        }
        if(!dp[n][m])
        {
    
            printf("no
    ");
            return 0;
        }
        int cnt=0;
        for(int j=m; j>=0; j--)
        {
            for(int i=n; i>=1; i--)
            {
                if(dp[i][j]&&!dp[i-1][j])//二维数组中每一列最顶部的那个T
                {
                    ans[cnt]=a[i];
                    cnt++;
                    j=j-a[i];
                    if(j==0)//找完结束
                    {
                        break;
                    }
                }
            }
        }
        for(int i=0; i<cnt-1; i++)
        {
            printf("%d ",ans[i]);
        }
        printf("%d
    ",ans[cnt-1]);
        return 0;
    }
  • 相关阅读:
    redis:string字符串类型的操作
    redis键值操作
    什么是redis?redis有什么用途?
    centos7下安装配置redis
    windows下安装和配置redis
    mysql5.7.20完全卸载 win10
    selenium+PhantomJS小案例—爬豆瓣网所有电影代码python
    用单进程、多线程并发、多线程分别实现爬一个或多个网站的所有链接,用浏览器打开所有链接并保存截图 python
    PhantomJS、CasperJS安装配置图文详解
    python:什么是单例?一个简单的单例
  • 原文地址:https://www.cnblogs.com/wkfvawl/p/11656399.html
Copyright © 2020-2023  润新知