第十六周学习总结

这周和上一周着重学习了背包问题,比普通的线性DP状态转移方程难写,也更难理解,但是听人说这东西不能多想,状态转移方程只要没有太大问题,其他交给计算机就行,

一、01背包

f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}

f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。
因为该状态f[i][v]只与上一状态有关,所以出现了简化(一维):f[v]=max{f[v],f[v-c[i]]+w[i]}
还有一点需要逆序!
因为求容量v的状态值时,只与前i-1件物品装入容量小于或等于v的状态有关,与大于v的状态无关。可先更新大容量的状态值。
我自己的理解就是,想求现在v容量i件物品最大价值,而需要比较的是上一状态i-1件物品v容量的价值(i不放背包)和i-1件物品v-c[i]容量的价值(i放入背包)。所以不能先把小容量的状态值修改了,否则会影响这一个状态寻找最大值

题目:Roy想要抢银行,给出每个银行拥有的金钱和被抓的概率,给出Roy最大能承受的被抓概率P,求Roy不被抓的前提下最多偷盗的钱财。可以承受的最大被抓的概率为p,即:如果安全的概率大于1-p则符合要求。

#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=10005;
double dp[N];
struct g {
    int v;
    double c;
} a[N];
int main()
{
    int n,t;
    double p;
    cin >> t;
    while(t--)
    {
        int sum=0;
        cin >>p>> n;
        for(int i=0; i<n; i++)
        {
            cin >>a[i].v>>a[i].c;
            sum+=a[i].v;
        }
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        for(int i=0; i<n; i++)
            for(int j=sum; j>=a[i].v; j--)
            {
                dp[j]=max(dp[j],dp[j-a[i].v]*(1-a[i].c));
            }
        for(int i=sum; i>=0; i--)
            if(dp[i]>1-p)
            {
                cout<<i<<endl;
                break;
            }
    }
    return 0;
}

作为背包问题中最简单的一类,状态转移方程写起来并不困难,只需要注意写代码时的一些细节即可。

二、完全背包

与01背包不同的地方在于物品有无限件,所以在考虑当前这件物品的个数时需要多一维数组,f[i][v-w[i]]正是我们需要的子问题,这时候的f[i][v]是由f[i][v-w[i]]推来的,所以用正序顺序循环。

f[i][j]=max{f[i-1][j-k*w[i]]+k*v[i]|0=<k<=V/w[i]}

时间优化:f[i][j]=max(f[i-1][j], f[i][j–wi]+vi)f[ i ][ v ]//为前 i 种物品恰好放入容量为 v 的背包的最大权值。

UVA147 Dollars - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;
double a;
int m;
int a_a[]={2000,1000,400,200,100,40,20,10,4,2,1};
long long ans[6001];
int main(){
	ans[0]=1;
	for(int i=0;i<=10;i++) 
		for(int j=a_a[i];j<=6000;j++)
			ans[j]+=ans[j-a_a[i]];
	while(1){
		cin>>a;
		if(a==0)break;
		printf("%6.2lf",a); 
		m=a*20;
		printf("%17lld\n",ans[m]);
	}
	return 0;
}

这道题不能忘记四舍五入,不然不会对

三、多重背包

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。(在完全背包的基础上,找到每件物品的最优个数从而就找到了最优解)
理解因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。
f[i][v]//表示前i种物品恰放入一个容量为v的背包的最大权值,则:

f[i][v]=max{ f[ i-1 ][ v-k*w[i] ] + k*val[ i ] | 0<= k <= n[ i ]}

题目

有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

#include <iostream>
using namespace std;

int N,V;
int dp[1010];
int main()
{
    scanf("%d%d",&N,&V);
    int v,w,s;
    for(int i=1;i<=N;i++)
    {
     
         scanf("%d%d%d",&v,&w,&s);
        for(int j=V;j>=0;j--){
            
            for(int k=1;k<=s&&k*v<=j;k++)
            {
               dp[j]=max(dp[j],dp[j-k*v]+k*w);
            }
        }
    }
    printf("%d",dp[V]);
    return 0;
}

四、分组背包

其实类似于01背包,对于一个物品有两种决策选或不选,但是分组背包是在01背包的基础上对物品进行了分组,并且每一组只能最多选择一个物品,所以可以用01背包的思想去思考分组背包.

分析:我们设f[i][j]为当前考虑到了第i组物品,剩余容里为j的背包能装物品的最大价值,那么很容易想到我们需要去枚举第i组物品,考虑选哪一个物品时最优的(或者不选),状态转移方程就是

f[k][v]=max{f[k-1][v],f[k-1][v-c[i]]+w[i]|物品i属于组k}

P1757 通天之分组背包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include<bits/stdc++.h>
using namespace std;
int v,n,t;
int x;
int g[205][205];
int i,j,k;
int w[10001],z[10001];
int b[10001];
int dp[10001];
int main(){
    cin>>v>>n;
    for(i=1;i<=n;i++){
        cin>>w[i]>>z[i]>>x;
        t=max(t,x);
        b[x]++;
        g[x][b[x]]=i;
    }
    for(i=1;i<=t;i++){
        for(j=v;j>=0;j--){
            for(k=1;k<=b[i];k++){
                if(j>=w[g[i][k]]){
                    dp[j]=max(dp[j],dp[j-w[g[i][k]]]+z[g[i][k]]);
                }
            }
        }
    }
    cout<<dp[v];
    return 0;
}

最近几周忙着复习,所以看的题目不是很多,每种背包就只看了两三道,而且以上的有些代码不是自己写出来的,只能说是搞懂了之后抄上来的,看到题目少导致题目理解不透彻,以这些题目结束这学期的算法课程有些草率了,所以如果可以我希望之后也可继续将算法学习下去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值