算法竞赛进阶指南 动态规划 背包

01背包

01背包的基本模型: n个物品 第i个物品的体积为vi 价值为wi 有一个容积为m的背包 要求选择一次额物品进入背包 使得不超过背包容量的物品总价值最大

01背包明显的特点就是一个物品只能选择一次

我们可以用已经处理了的物品数目和当前的的体积作为阶段 f[i,j]表示前i个物品中体积为j的最大价值的选法的价值 那么有f[i,j]=max{f[i-1,j],f[i-1,j-vi]+wi} 初始状态f[0,0]=0 其余初始化为负无穷 因为目标是最大价值 目标:max{f[n][m]};

代码实现:

代码中所有的&1 加上就是滚动数组的做法 节省空间 去掉的话就是正常做法

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
const int N=105,M=1e3+5;
int f[2][M],w[N],v[N]; //w价值 v代价
int n,m;
int main() {
    memset(f,0xcf,sizeof f);
    f[0][0]=0;
    cin>>m>>n;
    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&1][j]=f[(i-1)&1][j];
        }
        for(int j=v[i];j<=m;j++){
            f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j-v[i]]+w[i]);
        }
    }
    int ans=0;
    for(int j=0;j<=m;j++)
        ans=max(ans,f[n&1][j]);
    cout<<ans;
    return 0;
}

每个阶段开始的时候我们都进行了一次f[i-1][]和f[i][]的拷贝操作 我们可以省略掉第一维数组 只用一维数组f[j]表示背包放入总体积为j的物品的最大价值和;

这里一定要使用倒序循环才能保证每个最多被选择一次

因为如果正序 那么中途部分值会被更新影响 导致一个选择多次 变成完全背包 但是倒序的话每次更新用的都是原始数据 而不是更新数据;

cpp 复制代码
int f[N];
memset(f,0xcf,sizeof size);
f[0]=0;
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]);
    }
    int ans=0;
    for(int j=0;j<=m;j++){
        ans=max(ans,f[j]);
    }
}

完全背包问题

完全背包在01背包的基础上 每个可以选择多次

代码实现只需要改为正序即可

cpp 复制代码
int f[N];
memset(f,0xcf,sizeof size);
f[0]=0;
for(int i=1;i<=n;i++){
    for(int j=v[i];j<=m;j++){
        f[j]=max(f[j],f[j-v[i]]+w[i]);
    }
    int ans=0;
    for(int j=0;j<=m;j++){
        ans=max(ans,f[j]);
    }
}

P10955 正整数拆分

时间限制: 1.00s 内存限制: 512.00MB

复制 Markdown 退出 IDE 模式

题目描述

给定一个正整数 N,要求把 N 拆分成若干个正整数相加的形式,参与加法运算的数可以重复。

注意:

  • 拆分方案不考虑顺序;
  • 至少拆分成 2 个数的和。

求拆分的方案数 mod2147483648 的结果。

输入格式

一个正整数 N。

输出格式

输出一个整数,表示结果。

输入输出样例

输入 #1复制运行

复制代码
7

输出 #1复制运行

复制代码
14

说明/提示

1≤N≤4000

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int n;
const int N=4e3+1,mod =2147483648;
long long f[N];
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    memset(f,0,sizeof f);
    f[0]=1;
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j++){
            f[j]=(f[j]+f[j-i])%mod;
        }
    }
    cout<<(f[n]>0?f[n]-1:2147483647)<<'\n';
    return 0;
}
 

多重背包问题

多重背包与01背包不同的是 每一种物品可能有多个

我们有几种做法

1.直接拆分法

将同一种物品 看成独立的物品 当成01背包来做 但是这样很明显效率比较低下

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
int n,m;
const int N=4e3+1,mod =2147483648;
long long f[N],c[N],v[N],w[N]; //c表示个数
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>w[i]>>v[i]>>c[i];
    }
    memset(f,0xcf,sizeof f);
    f[0]=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=c[i];j++)
            for(int k=m;k>=v[i];k--)
                f[k]=max(f[k],f[k-v[i]]+w[i]);
    long long ans=0;    
    for(int i=1;i<=m;i++)ans=max(ans,f[i]);
    return 0;
}
 

2.二进制拆分法

3.单调队列

P10973 Coins

时间限制: 1.00s 内存限制: 512.00MB

复制 Markdown 退出 IDE 模式

题目描述

银国的人们使用硬币。他们有面值分别为 A1​,A2​,A3​,...,An​ 的硬币。有一天,托尼打开了他的储蓄罐,发现里面有一些硬币。他决定去附近的商店购买一块非常漂亮的手表。他想要支付准确的价格(不找零),而他知道手表的价格不会超过 m 。但他不知道手表的确切价格。

你需要编写一个程序,读取 n、m、A1​,A2​,A3​,...,An​ 以及对应的数量 C1​,C2​,C3​,...,Cn​ (表示托尼拥有的每种面值的硬币数量),然后计算托尼可以用这些硬币支付的价格数量(从 1 到 m 的所有价格)。

输入格式

输入包含多个测试用例(不超过 25 组)。每个测试用例的第一行包含两个整数 n(1≤n≤100) 和 m(m≤100000)。第二行包含 2n 个整数,分别表示 A1​,A2​,A3​,...,An​ 和 C1​,C2​,C3​,...,Cn​(1≤Ai​≤100000,1≤Ci​≤1000)。最后一个测试用例以两个零结尾。

输出格式

对于每个测试用例,在单独的一行输出答案。

输入输出样例

输入 #1复制运行

复制代码
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

输出 #1复制运行

复制代码
8
4

我们可以发现若前i中硬币能拼成面值j 只有两类情况

我们设置f[i]表示i是否可以被形成

1.前i-1中硬币能形成面值j 那么i阶段开始 f[j]已经变成true

2.使用了第i中硬币即在i的递推中f[j-a[i]] 为true从而f[j]变为true

我们可以考虑一种贪心策略 设d[j]表示f[j]在阶段i时为true至少要用多少枚第i中硬币 并且尽可能选择情况1 也就是f[j-a[i]] 为true时 如果f[j]已经变为true 则不执行dp的转移 并令d[j]=0; 否则才执行f[j]=f[j]orf[j-a[i]]的转移 并令used[j]=used[j-a[i]] +1;

cpp 复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int f[100010],d[100010],a[110],c[110],n,m,x,y,ans;
int main() 
{
	while(cin>>n>>m&&n!=0)
	{
		for(int i=1;i<=m;i++) f[i]=0;
		for(int i=1;i<=n;i++) cin>>a[i];
		for(int i=1;i<=n;i++) cin>>c[i];
		f[0]=1;
		for(int i=1;i<=n;i++)
		{
			x=a[i],y=c[i];
			for(int j=0;j<=m;j++) d[j]=0;
			for(int j=0;j<=m-x;j++)
				if(f[j]==1&&d[j]<y&&f[j+x]==0)
					f[j+x]=1,d[j+x]=d[j]+1;
		}
		ans=0;
		for(int i=1;i<=m;i++)
			if(f[i]==1) ans++;
		cout<<ans<<endl;
	}
}

分组背包

基本模型:给定n组物品 第i组有ci个物品 第i组的第j个物品的体积为vij 价值为wij 有一个容积为m的背包 要求选择若干个物品放入背包,使得每组之多选择一个物品且物品总体积不超过m的前提下 物品的价值总和最大

状态转移方程:

f[i,j]=max{f[i-1,j],max{f[i-1,j-vjk]+wik}}

类似于前面几个背包模型 我们可以省略第一维度 然后j倒序 把k的循环放在j的内层 这样是分组背包 否则会变成多重背包

cpp 复制代码
memset(f,0xcf,sizeof f)
f[0]=0;
for(int i=1;i<=n;i++){
	for(int j=m;j>=0;j--){
		for(int k=1;k<=c[i];k++){
			if(j>v[i][k])
				f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
		}
	}
}
相关推荐
程序员-King.2 小时前
day124—二分查找—最小化数组中的最大值(LeetCode-2439)
算法·leetcode·二分查找
predawnlove2 小时前
【NCCL】4 AllGather-PAT算法
算法·gpu·nccl
驱动探索者2 小时前
[缩略语大全]之[内存管理]篇
java·网络·算法·内存管理
·云扬·2 小时前
MySQL Join关联查询:从算法原理到实战优化
数据库·mysql·算法
bbq粉刷匠2 小时前
二叉树中两个指定节点的最近公共祖先
java·算法
Alsn863 小时前
29.Java中常见加解密算法的基本实现
java·开发语言·算法
1001101_QIA3 小时前
OpenMP学习笔记
算法
Coovally AI模型快速验证3 小时前
YOLO11算法深度解析:四大工业场景实战,开源数据集助力AI质检落地
人工智能·神经网络·算法·计算机视觉·无人机
(❁´◡`❁)Jimmy(❁´◡`❁)3 小时前
【算法】 二分图理论知识和判断方法
c++·算法