5月16日一周学习总结

5月8日一周学习总结

背包问题

这周是依旧是背包问题的训练,包括01背包,完全背包,多重背包,还有一些背包问题的变形。这周较为清晰的理解了完全背包问题中的一维dp方程,与01背包循环顺序相反,这样就会按照体积由小到大改变背包价值,等到了计算统一循环大体积背包时就可以进行利用。

for(int j=1;j<=n;j++)
     for(int k=w[j];k<=s;k++)
         dp[k]=max(dp[k],dp[k-w[j]]+v[j]);

字面意思可以理解为:判断第n种物品的拿放以及数量,先按照从小到大背包体积来看,当前可以装第n种物品的最大价值,当进行下一个体积判断时,就可以将上一个数据取出来使用,就会得到多个n物品放入当前体积的背包中获得的最大价值,而01背包因为是按照体积从大到小,所以使用的数据是上一层循环的,即第n-1个物品的最大价值情况。
对于多重背包则可以看作是完全背包的一个变形,在完全背包的基础上添加限制条件,如物品件数以及价值限制。

01背包问题

带有限制条件的01背包变形
例题:给出钱数和物品数以及每个物品的价格和购买限制(当你手头钱数低于此标准,禁止购买此商品)以及艺术价值,求可以得到最大的艺术价值。
根据题意可以初步判断是01背包问题,钱数即为背包体积,物品价格即为单个物品的体积,但是对于购买限制这一条件,这个题目要想要遍历所有情况,由于是01背包,背包的上次循环需要存在才能对当前循环产生影响,必须让限制条件小的先算,同时还有可能出现限制条件相同的情况,所以还要对价格大的先选(01背包倒序)所以上来先对物品排序

bool cmp(node a1,node a2)
{
    return a1.q-a1.v<a2.q-a2.v;
}

然后开始构思动态转移方程,这个比较好想,基本上照搬了01背包问题的模板

sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        for(int j=m;j>=a[i].q;j--)
			{
				s[j]=max(s[j],s[j-a[i].v]+a[i].w);
			}
    }

这个题目的难点在于排序,个人感觉属于贪心算法和动态规划的混合,以动态规划为环境,在此基础上进行贪心,对于这一步还是存在一点困惑,很多题解中对于这一步的理解都不太一样,还是要好好消化一下这个知识点。
01背包中还有计算最优解数量的问题:

cin>>n>>m;
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=m;i++)
            b[i]=1;
        for(int i=0;i<n;i++)
        {
           cin>>a[i];
        }
        for(int i=0;i<n;i++)
        {
            for(int j=m;j>=a[i];j--)
            {
                if(dp[j]<dp[j-a[i]]+1)
                {dp[j]=dp[j-a[i]]+1;
                b[j]=b[j-a[i]];}
                else if(dp[j]==dp[j-a[i]]+1)
                {
                    b[j]+=b[j-a[i]];
                }
            }
        }
        if(dp[m]==0)
            cout<<"Sorry, you can't buy anything."<<endl;
        else
        cout<<"You have "<<b[m]<<" selection(s) to buy with "<<dp[m]<<" kind(s) of souvenirs."<<endl;

这类问题可以用结构体,也可以用两个一位数组,一个记录当前最优解,一个记录当前最优解的数目。
当dp数组改变时,数目数组也要发生改变,变为改变后dp数组对应的最多数目。当相同时则要让当前数目加等于新数目。(这道问题一开始一直过不了,因为一直忽略选0个物品时只有1种方法,要在此基础上才能构造dp方程并使其有意义)
最后是划分区间的背包问题,即一个区间的物品只能选择一个。
例题:给出一个复习时间和考试科目以及每门科目复习不同天数得到的分数,求最大的到多少分。

memset(dp,0,sizeof(dp));
            for(int i=1;i<=n;i++)
            {
                for(int j=1;j<=m;j++)
                {
                    cin>>a[i][j];
                }
            }
            for(int k=1;k<=n;k++)
            {
                for(int j=m;j>=1;j--)
                    for(int i=1;i<=j;i++)
                {
                  dp[j]=max(dp[j],dp[j-i]+a[k][i]);
                }
            }
            cout<<dp[m]<<endl;

这个题目的区间划分:第一层是不同科间的循环,第二层的循环是v从已知最大天数到0,第三层是组内循环也就是每个课程选几天的得分。

完全背包问题

个人觉得这类问题中较为典型的便是硬币问题。
例题:给出一堆不同面额的硬币以及一个钱数,问有多少种方法可以组成这一面额的钱数。

long long dp[10005];
int a[11]={1,2,4,10,20,40,100,200,400,1000,2000};
int main()
{
    double n;
    while((cin>>n)&&(n!=0))
    {
     int m=n*20;
     memset(dp,0,sizeof(dp));
     dp[0]=1;
     for(int i=0;i<11;i++)
     {
         for(int j=a[i];j<=m;j++)
         {
             dp[j]+=dp[j-a[i]];
         }
     }

这是最经典的一道问题,dp数组记录的是当前钱币的组成数目,当循环进行到当前面值的硬币时,会继承由上种面值硬币组成钱数的最大数量。
这种硬币题目还有一些带有限制条件的问题:问题不变,但是会有很大的数据存在,这时候就需要拆分数据。

long long a[2000],b[2000];
int main()
{
    int n,m;
    long long inf=1e18;
    cin>>n>>m;
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    a[0]=1;
    for(int i=1;i<=m;i++)
       for(int j=i;j<=n;j++)
       {
            b[j]=b[j]+b[j-i]+(a[j]+a[j-i])/inf;
            a[j]=(a[j]+a[j-i])%inf;
       }
    if(b[n])
        printf("%lld",b[n]);
    printf("%lld\n",a[n]);
    return 0;
}

针对大数据,将其分成两部分,一部分是能够计算的最大数额,另一部分是超出的面额。(最后输出的时候只需要让两个输出连在一起即可)

本周学习总结

这周一直在练习背包问题,见识到了许多背包问题的变形,一般是在背包问题的基础上添加限制条件,然后将限制条件在循环中表现出来,值得注意的是,限制条件一般会与背包顺序形成联系,所以在解决问题时要以背包顺序为情境,然后按照限制条件进行动态规划处理。这周还学习了二分查找,个人感觉这种算法是对于暴力中逐一判断的优化,类似于函数中的找最值,所以应该要求这类问题必须具有单调性,然后不断缩小区间,直到锁定到一个数字上,这时候二分查找完毕,得到答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值