zoukankan      html  css  js  c++  java
  • dp之完全背包poj1787(完全背包以及路径记录 推荐)

    题意:有四种硬币,1分,5分,10分,25分,分别有a,b,c,d种,给出一个n分钱,要求你求出组成n分钱最多需要的硬币数量,并且输出组成它的各种硬币的数量......

    学到的东西:这个题目我是用两种方法做的,一个是完全背包,一个是多重背包。做完这个题目,我对背包的理解可以说上了个层次......还有记录路径的方法,反过来求出各个硬币的数量,都是我以前做的题目没有涉及到的.......

    要求出各个硬币有多少种,只需要记录路径,在开一个数组统计p-path[p],为什么可以如此?很容易想到path[p]=p-v[i]

    如此,p-path[p]==p-(p-v[i])==v[i],而v[i]正好是硬币的价值........这道题目令我思考到一个东西,一般的背包问题的初始值都是将dp全部赋值为0的,而在这边dp[0]==1,并且在动态转移的过程中,还限制了dp[j]>0才可以转移,与背包的模板的动态转移不同啊?为什么要这样呢?

    在一般的dp题目里面,有体积,价值,所要求的不是最大价值就是最小价值,而定义的dp[i][j]意义是装有i件物品,体积为j的最大值为dp[i][j],简化为一维的dp[j]代表的是在体积为j的时候最大价值为dp[j]。仔细观察,可以发现,在dp[j-v[i]]时,dp[j]本身就可以从dp[j-v[i]]那边得到值,因为dp[j-v[i]]这个地方,它可以组成dp[j]......也就是说,它不需要去判断在dp[j-v[i]]前面是否有数可以组成dp[j-v[i]],因此dp[j-v[i]]为不为0,不影响最终结果......

    而这个题目却不同,需要赋值dp[0]=1;在动态转移的时候还得保证dp[j-v[i]]>0,这是因为题意是要求组成n分钱需要的最多硬币数量,那么你要可以组成dp[n],dp[n-v[i]]必须要可以组成,否则就会出错.......同理,这道题目原理如此,在做给出n分钱,每种钱币有a,b,c,.....种,求组成n分钱最多的种数,也是要赋值dp[0]=1的,原理是一样的........

    这告诫我,在做dp题目时,要仔细思考好其前后的关系,以及中间推导至最后的关系.....

    完全背包代码:

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    int dp[10010],ans[10010],num[10010],path[10010];
    int p,c[5]={0,1,5,10,25},t[5];
    int main()
    {
        while(scanf("%d %d %d %d %d",&p,&t[1],&t[2],&t[3],&t[4])>0&&(p+t[1]+t[2]+t[3]+t[4]))
        {
            memset(dp,0,sizeof(dp));
            memset(ans,0,sizeof(ans));
            memset(path,0,sizeof(path));
            dp[0]=1;
            for(int i=1;i<=4;i++)
            {
                memset(num,0,sizeof(num));
                for(int j=c[i];j<=p;j++)
                if(dp[j-c[i]]&&dp[j-c[i]]+1>dp[j]&&num[j-c[i]]<t[i])   //一般来说,完全背包的硬币是没有限制的,后一个数必然可以由前面的某个数组成,所以也就不需要dp[j-c[i]]>0,
    但是,这次用到的完全背包其硬币数受到了限制,也就导致有些数根本不可能组成,所以要把这些数排除 { dp[j]=dp[j-c[i]]+1; num[j]=num[j-c[i]]+1; path[j]=j-c[i]; } } int i=p; if(dp[p]>0) { while(i!=0) { ans[i-path[i]]++; i=path[i]; } printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters. ",ans[1],ans[5],ans[10],ans[25]); } else printf("Charlie cannot buy coffee. "); } return 0; }
     多重背包代码:
    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    struct node
    {
        int num;
        int cout;
        int dw;
    }w[20001];
    int dp[20001],c[5]={0,1,5,10,25},t[5],path[20001][2],ans[20001];
    int p;
    int main()
    {
        while(scanf("%d%d%d%d%d",&p,&t[1],&t[2],&t[3],&t[4])>0&&(p+t[1]+t[2]+t[3]+t[4]))
        {
            int cnt=0;
            for(int i=1;i<=4;i++)
            {
                int k=1;
                while(t[i]-k>0)
                {
                    w[cnt].num=k*c[i];
                    w[cnt].cout=k;
                    w[cnt++].dw=c[i];
                    t[i]-=k;
                    //if(i==1)
                    //printf("%d
    ",w[cnt-1].dw);
                    k*=2;
                }
                
                w[cnt].num=t[i]*c[i];
                w[cnt].cout=t[i];
                w[cnt++].dw=c[i];
                //if(i==1)
                //    printf("%d
    ",w[cnt-1].dw);
            }
            memset(dp,0,sizeof(dp));
            memset(path,0,sizeof(path));
            memset(ans,0,sizeof(ans));
            dp[0]=1;
            for(int i=0;i<cnt;i++)
            {
                for(int j=p;j>=w[i].num;j--)
                if(dp[j-w[i].num]>0&&dp[j]<dp[j-w[i].num]+w[i].cout)  //多重背包的二进制拆分其原理就是转化成01背包来处理,而01背包是从n推导至0,在i==1时,dp[n-w[i].num]+w[i].cout>dp[n]除非w[i].cout==0,否则就是必然的,但是同时,你要可以组成dp[n],那么你首先要可以组成dp[n-w[i].num],那么也就是说dp[n-w[i].num]必须要>0,才可以进行动态转移 
                {
                    dp[j]=dp[j-w[i].num]+w[i].cout;
                    //printf("%d %d %d %d
    ",w[i].num,w[i].dw,w[i].cout,dp[j]);
                    path[j][0]=j-w[i].num;
                    path[j][1]=i;
                }
            }
            //printf("%d
    ",dp[p]);
            if(dp[p]!=0)
            {
                while(p!=0)
                {
                    int tmp=p-path[p][0];
                    int j=path[p][1];
                    ans[w[j].dw]+=w[j].cout;
                    p=path[p][0];
                }
                 printf("Throw in %d cents, %d nickels, %d dimes, and %d quarters.
    ",ans[1],ans[5],ans[10],ans[25]);
            }
            else printf("Charlie cannot buy coffee.
    ");
        }
        return 0;
    }
    
     
  • 相关阅读:
    第五课补充01——持久化
    第六课补充01——主从复制原理,哨兵机制
    第五课作业——持久化
    矢量图网站
    WPF中获取控件之间的相对位置
    如何使用Prism框架的EventAggregator在模块间进行通信
    WPF中XAML中使用String.Format格式化字符串示例
    Win32 API中的user32.dll中的ShowWindow方法参数整理
    C# XML序列化帮助类代码
    建议2:使用默认转型方法
  • 原文地址:https://www.cnblogs.com/ziyi--caolu/p/3211147.html
Copyright © 2011-2022 走看看