【不会吧不会吧,真的有人不会动态规划吗?】

趁此机会整理一下思路。

背包问题合集

01背包

P1048 [NOIP2005 普及组] 采药 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

 看这个图应该就可以理解01背包问题了

需要注意的一个点

1.f[i-1][j-v[i]]+w[i]下标不要越界,反映到题目的意思是,如果我么要选择第i个物品,前提条件就是在上一步处理完i-1情况下,背包的空间还可以放得下第i个物品

二维

#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;

int n,m;
int v[N],w[N];
int f[N][N];

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

一维优化

            f[i][j]=f[i-1][j];
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);

从这一段可以看出我们每次更新状态的时候,只用到了上一级的状态,打表来看会更直接。

那么是否可以使用滚动数组来优化捏?答案是肯定的。

上文橘色高光注意!

我们观察一下j-v[i]肯定是小于j的,如果还是按正向更新的话,更新j时所使用的j-v[i]状态已经在前面更新过一次了。

跟简单,我们只需要j-v[i]在后面更新就可以了,很容易想到,反向遍历

这就得出了一维优化

#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;
int n,m;
int v[N],w[N],f[N];

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++){
        for(int j=m;j>=v[i];j--){
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    cout<<f[m];
    return 0;
}

完全背包

3. 完全背包问题 - AcWing题库

与01背包问题相比,这里的物品有无限多个了。那么只需要小小地改动一下集合划分了。

 与上文一样注意一下标越界(合法性)问题。k*v[i]<=j 

就这样一个三重循环就出来啦!

朴素

#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;
int n,m;
int v[N],w[N];
int f[N][N];

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            for(int k=0;k*v[i]<=j;k++){
                f[i][j]=max(f[i][j,f[i-1][j-k*v[i]+k*w[i]);
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

 优化

这里的优化不是怎么看得出来。

那我们来做一个小小的推导,前面都是拿i-1 和i 两种相邻状态作比较。

这次也是

这里就不是利用上一个i-1状态,而是同一行前面的状态了。(从打表来看)

suoyi 01问题规避的东西我们正好需要

#include<iostream>
#include<algorithm>
using namespace std;

const int N=1010;
int n,m;
int v[N],w[N];
int f[N];

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];

    for(int i=1;i<=n;i++){
        for(j=v[i];j<=m;j++){
            f[j]=max(f[j],f[j-v[i]+w[i]);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

比较期待多重背包的优化wwww 

多重背包

与01、完全背包相比,多重背包依旧是在物品数量上面有了变化。

物品的数量变成了固定的数目。

朴素算法

#include<iostream>
#include<algorithm>
using namespace std;

const int N=110;
int n,m,s[N],v[N],w[N],f[N][N];

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++){
                f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

 

 

朴素算法和之前的很像就不赘述了。

优化

 

这里的数据太大,会TLE。

采用的方式是二进制优化。

可以想一下计算机内部要将数字转化成二进制来存储,二进制只有0和1不就对应着01背包问题吗?

由此我们有了一种算法,将每件物品的数目转化成二进制存储。

24=2^4+8 =2^4+2^3   前 24次  后4次

65=2^6+1=2^6+2^0   前 65次   后 5次

1+2^1+2^2+2^3......+2^(k-1)=2^k -1

当然可以得出n----> ceil ln(n)        (好像有一个函数ceil 是向上取整  floor是向下取整 )\

www,不知道怎么说。

0~n都可以用ln(n)位二进制来表示

依次,将每个物品的数量都可以打包成2^0 2^1......2^ln(n)+c种01背包问题。

复杂度就转化成了N*M*ln(n)

#include<iostream>
#include<algorithm>
using namespace std;
const int N=25000,M=2100; 

int n,m;
int v[N],w[N];
int f[N];

int main(){
	cin>>n>>m;
	int cnt=0;
	for(int i=1;i<=n;i++){
		int a,b,s;
		cin>>a>>b>>s;
		int k=1;
		while(k<=s){
			cnt++;
			v[cnt]=a*k;
			w[cnt]=b*k;
			s-=k;
			k*=2;
		}
		if(s>0){
			cnt++;
			v[cnt]=a*s;
			w[cnt]=b*s;
		}
	}
	n=cnt;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=v[i];j--)
			f[j]=max(f[j],f[j-v[i]]+w[i]);
	}
	cout<<f[m];
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值