《暑假每日一题》Week 1: 6.20 - 6.26
前言
- 环境:VS 2019
- 大一,水平不高,单纯记录暑假生活。
- AcWing题库
Day 1 :01背包问题
关键词
- 每件物品只能使用一次
- 总体积不超过背包容量(不是正好相等哦~)
- 求的是总价值最大(不是最多能放多少个东西)
- 放进一个物品是能获得价值,不是白嫖
解题思路
- 算法:(二维动态规划) O(n^2)
AC代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N]; //每个物品的体积
int w[N]; //每个物品的价值
int f[N][N]; //状态转移方程,上面有详细解释
int main(){
int n, m;
cin >> n >> m; //输入物品数量和背包容量
for(int i = 1;i <= n;i ++) scanf("%d%d",&v[i],&w[i]); //输入每个物体的体积和价值
for(int i = 1;i <= n;i ++){
for(int j = 0; j <= m; j ++){
if(j < v[i]) f[i][j] = f[i - 1][j]; //不合法,不包括i
else f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]); //包括i
}
}
cout << f[n][m] << endl; //输出答案
return 0;
}
Day 2 : 完全背包问题
关键词
- 集合 : 用前i个物品 ,装进体积为j 的所有方案的集合 f[i][j]
- 属性 : 所选方案中物品价值的最大值
- 计算 | 集合的划分 : 第i个物品选择次数来划分
- 完全 : 每个物品可以选择无数次,但是不能超过背包的体积
解题思路
AC代码
朴素写法1
#include <iostream>
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 = 1; j <= m; j ++ ){
f[i][j] = f[i - 1][j]; // 第i个物品选0次
for(int k = 1; k <= j / v[i]; k ++){
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]); // 选k次
}
}
cout << f[n][m] << endl;
return 0;
}
上面是刚开始采用了朴素写法 果然TLE了,于是下面进行了代码优化。
朴素写法2
#include <iostream>
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 = 1; j <= m; j ++ ){
f[i][j] = f[i - 1][j]; // 第i个物品选0次
if (j >= v[i])
f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]); // 第i个物品选其他次
//针对朴素写法1 的f[i][j] 的等价变换 f[i][j-v]推导得出
}
cout << f[n][m] << endl;
return 0;
}
空间优化(最终代码)
#include <iostream>
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 (int j = v[i]; j <= m; j ++ ) // 注意j的初值
f[j] = max(f[j], f[j - v[i]] + w[i]);
// 内循环递增
// 恒等式 f[j] = f[j]省略没写 ; if(j >= V[i])的条件判断 直接写到for里面
cout << f[m] << endl;
return 0;
}
Day 3 :多重背包问题1
解题思路
闫氏DP分析法
一、状态表示:f[i][j]
- 集合:从前i个物品中选,且总体积不超过j的所有方案的集合.
- 属性:最大值
二、状态计算:
- 思想-----集合的划分
- 集合划分依据:根据第i个物品有多少个来划分.含0个、含1个···含k个.
状态表示与完全背包朴素代码一样均为:
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
AC代码
也没有想太多 暴力就完事了(嗯~暴力) 本题目n = 100, n3=1e6n3=1e6 没超时
#include <iostream>
using namespace std;
const int N = 110;
int v[N],w[N],s[N];
int n,m;
int f[N][N];
int main(){
ios::sync_with_stdio(false); cin.tie(0);
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 = 0; j <= m; j++){
for(int k = 0; k <= s[i] && k*v[i] <= j; k++){ // k = 0包含了 f[i-1][j]的情况
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]] + w[i]*k);
}
}
}
cout << f[n][m] << endl;
return 0;
}
Day 4 : 数组中数值和下标相等的元素
(摆烂的Day4)
解题思路
单调性 二分
mid左边的数会小于mid,mid右边的数会大于mid,符合单调性
AC代码
(该题选自LeetCode,所以代码提交格式不太一样)
class Solution {
public:
int getNumberSameAsIndex(vector<int>& nums) {
int l = 0,r = nums.size() - 1;
while (l < r){
int mid = l + r >> 1;
if (mid <= nums[mid])r = mid;
else l = mid + 1;
}
if (l == nums[l])
return l;
else
return -1;
}
};
Day 5:多重背包问题ll
解题思路
因为数据加大,三重循环会超时,这时候需要用到二进制优化转为01背包
设某件物品ii 的sisi = 7 , 二进制为111111 , 20=1,21=2,22=420=1,21=2,22=4
此时我们将该物品拆分,
k = 1即当成1件物品(wi∗1,vi∗1wi∗1,vi∗1),
k = 2当成一件物品(wi∗2,vi∗2wi∗2,vi∗2),
k = 4当成一件物品(wi∗4,vi∗4wi∗4,vi∗4)
我们就可以 任意选 这被拆分得到的三件物品 凑成所有的情况,即选该物品i被选任意一个数量的情况
注意:
这些被拆分的物品,已经是像01背包一样,互不干涉的,并且只能选一次
例如:
0=0,1=1,2=2,3=1+2,4=4,5=1+4,6=2+4,7=1+2+30=0,1=1,2=2,3=1+2,4=4,5=1+4,6=2+4,7=1+2+3
再比如另一个物品i, si=11si=11, 就必须拆成k=1,k=2,k=4,k=4k=1,k=2,k=4,k=4
任意选这些被拆分得到的物品同样可以凑出所有情况, 即 0 ~ 11的所有情况
AC代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N];
int f[M];
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;
}
//11 : 1 2 4 4多出的4
if(s > 0){
cnt ++;
v[cnt] = a * s;
w[cnt] = b * s;
}
}
n = cnt;
for(int i = 1; i <= n; i ++) //01背包
for(int j = m; j >= v[i]; j--){
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
cout << f[m] << endl;
return 0;
}
Day 6 : 混合背包问题
解题思路
完全背包虽然说我们可以选的个数是无穷个,但是因为有体积限制所以我们完全背包实际上也是有限的
因此完全背包可以看作每个物品可以选择的个数为m(总体积)/vi[i]的一个多重背包
然而多重背包可以转换为等价的01背包
所以这题的三个背包都可以转化为01背包
所以整个问题可以转化为为一个01背包问题
AC代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, V, w[N], v[N], cnt, dp[N];
int main(){
cin >> n >> V;
for (int i = 1; i <= n; i++){
int a, b, s;
cin >> a >> b >> s;
if (s < 0) s = 1;//其实就是s==-1的情况,只能用一次,于是我们把它转化为数量为1的多重背包.
if (s == 0) s = V / a;//完全背包,可以放的东西总体积不超过V,于是可以转化为数量为V/a(C++中int变量相除向下取整)的多重背包.
int k = 1;
while (k <= s){//常规多重背包处理,用二进制优化
cnt ++;
v[cnt] = k * a;
w[cnt] = k * b;
s -= k;
k *= 2;
}
if (s > 0)
{
cnt ++;
v[cnt] = s * a;
w[cnt] = s * b;
}
}
for (int i = 1; i <= cnt; i++)//01背包处理
{
for (int j = V; j >= v[i]; j--)
{
dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
}
}
cout << dp[V] << endl;
return 0;
}