202209-2-何以包邮?
问题
思路一:类穷举,结合set集合
枚举出所有的组合的总价格,再取满足包邮的最小总价格(100分 ):
搜索发现此题多用递归,动态规划(0-1背包)实现,自己尝试用枚举(set集合结合类似穷举的方式)居然也100分了
⚠️:关键点——
set集合
、
书的价格列表,价格由低到高排序
用set集合优化重复的组合的总价格
遍历书的价格列表,后面组合的总价格基于前面的组合的总价格的集合
实现
n, x = map(int, input().split()) # n为购物车中图书数量,x为包邮价格条件
a = [0 for _ in range(n)] # 书的价格列表(价格由低到高)
for i in range(n):
a[i] = int(input().strip())
a.sort()
# 枚举出所有的可能价格
cost_set = set() # 存储各种搭配的价格
temp_list = [] # 暂存价格(避免循环时改变迭代器)
for i in a:
if i == a[0]: # 最小的,加入即完毕
cost_set.add(i)
continue
for j in cost_set: # 把可能的情况暂存,避免循环时改变迭代器
temp_list.append(i+j)
for k in temp_list:
cost_set.add(k)
cost_set.add(i) # 注意,最后加自己
# 找恰大于x的值
for i in sorted(list(cost_set)):
if i >= x:
print(i)
break
1、
strip()
和split()
strip()
: 去掉字符串中的空白字符(包括换行符、制表符、空格等)。split()
: 将字符串按照指定分隔符分割成多个子串,并返回一个列表。如果不指定分隔符,则默认使用空白字符(包括换行符、制表符、空格等)作为分隔符。
思路二:类0-1背包动态规划
⚠️有点巧妙,求最大的总价格,从而转换为简化的0-1背包
-
传统0-1背包有n种物品,每种物品有价值和重量,总重量不超过容量情况下,使得总价值最大
- 前i个物品放入容量为j的背包中所获得的最大价值
-
本题n本书,每本书有价值,选取的总价格不超过 sum - 包邮价格x,使得总价格最大
- 前i个物品放入总价为j的背包中所获得的最大价值
方法一:二维数组、建立n个物品 * 最大价格 的二维数组
方法二:一维数组、定一个数组,长度为
sum()
,求对应的书本的总价格
实现
一维数组——正序
n, x = map(int, input().split()) # n为购物车中图书数量,x为包邮价格条件
a = [0] + [0 for _ in range(n)] # 书的价格列表(价格由低到高)
for i in range(1, n+1):
a[i] = int(input().strip())
total = sum(a) # 最大价格
matrix = [0 for _ in range(total+1)]
next_matrix = [0 for _ in range(total+1)]
res = total # 满足条件的最低价格
for i in range(1,n+1): # i 为价格列表序号
for j in range(1,total+1): # j为容量,matrix[j]当前容量下的最大总价格
if(j >= a[i]):
next_matrix[j] = max(matrix[j],matrix[j-a[i]]+a[i])
matrix = next_matrix
for i in matrix:
if i >= x:
print(i)
break
一维数组——倒序
n, x = map(int, input().split()) # n为购物车中图书数量,x为包邮价格条件
a = [0] + [0 for _ in range(n)] # 书的价格列表(价格由低到高)
for i in range(1, n+1):
a[i] = int(input().strip())
total = sum(a) # 最大价格
matrix = [0 for _ in range(total+1)]
res = total # 满足条件的最低价格
for i in range(1,n+1): # i 为价格列表序号
for j in range(total,0,-1): # j为容量,matrix[j]当前容量下的最大总价格
if(j >= a[i]):
matrix[j] = max(matrix[j],matrix[j-a[i]]+a[i])
for i in matrix:
if i >= x:
print(i)
break
由于定义的状态转移方程
matrix[j] = max(matrix[j],matrix[j-a[i]]+a[i])
,即matrix[j]依赖于matrix[j-a[i]]:
- 正序损伤前面的数组单元,需用
next_matrix
缓存- 倒序则不会
⚠️:个人测试了几组正序是没问题,可通不过,有点奇怪
!!!建议用倒序的
二维数组——Python
n, x = map(int, input().split()) # n为购物车中图书数量,x为包邮价格条件
a = [0] + [0 for _ in range(n)] # 书的价格列表(价格由低到高)
for i in range(1, n+1):
a[i] = int(input().strip())
total = sum(a) # 最大价格
matrix = [[0 for _ in range(total+1)] for _ in range(n+1)]
res = total # 满足条件的最低价格
for i in range(1,n+1): # i 为价格列表序号
for j in range(1, total+1): # j为容量
if(a[i] <= j):
matrix[i][j] = max(matrix[i-1][j], matrix[i-1][j-a[i]]+a[i])
else:
matrix[i][j] = matrix[i-1][j]
if(x <= matrix[i][j] < res): # 找到满足条件且最低的总价格
res = matrix[i][j]
print(res)
定义一个二维数组,i表示第i本书(1<=i<=n图书数量)j表示可用的钱(1<=j<=sum最大价格)
以下面数据为输入为例子
3 10 4 2 9
得到matrix[4][16]二维数组如下
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4], [0, 0, 2, 2, 4, 4, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6], [0, 0, 2, 2, 4, 4, 6, 6, 6, 9, 9, 11, 11, 13, 13, 15]]
构造matrix过程中,找到满足条件且最低的总价格res
- 缺点: 浪费大量空间
二维数组——C++
嵌套for 用Python有点容易超时,得复习用C\C++了
- C++版本
# include<iostream>
# include<algorithm>
using namespace std;
const int N = 31, M = 3e5 + 1; // 因n不超过30,总价的关系300001
int a[N];
int matrix[N][M];
int main(){
int n, x; // n为购物车中图书数量,x为包邮价格条件
cin >> n >> x;
int total = 0; // 最大价格
for(int i=1;i<=n;i++){
cin >> a[i];
total = total + a[i];
}
int res = total; //满足条件的最低价格
for(int i=1;i<=n;i++){ //i 为价格列表序号
for(int j=1;j<=total;j++){ //j为容量
if(a[i] <= j){
matrix[i][j] = max(matrix[i-1][j],matrix[i-1][j-a[i]]+a[i]);
}else{
matrix[i][j] = matrix[i-1][j];
}
if(x <= matrix[i][j] && matrix[i][j] < res){ // 找到满足条件且最低的总价格
res = matrix[i][j];
}
}
}
cout << res;
// int m;cin>>m; //mac OS g++断点
return 0;
}
与Python对比:
- 头文件、类型声明、主函数入口
int main(){return 0;}
、结尾;
⚠️:
- 声明数组
int a[N];
有默认0- 逻辑判断需要
&&
Appendix
前缀和减少时间复杂度
第二题:正常思路是:最少得需要两个For循环,要说两个For循环也不难,难就难在两个for就超时,例如2020-12-2期末预测之最佳阈值,还有2021-04-2邻域均值,都用了前缀和来减少时间复杂度,这里可以学习一下前缀和
前缀和(Prefix Sum)是指数组中每个元素的前缀和,即从数组开头一直加到当前元素的总和。前缀和可以使用一个辅助数组来保存,可以在常数时间内求得一个区间的和,从而减少时间复杂度。
在Python中,可以使用列表(List)来保存前缀和。例如,给定一个列表nums
,可以使用以下代码来计算其前缀和:
prefix_sum = [0] * (len(nums) + 1)
for i in range(1, len(nums) + 1):
prefix_sum[i] = prefix_sum[i-1] + nums[i-1]
动态规划——经典0-1背包问题
- 描述
有一个背包,它的容量为C(Capacity)。现在有n种不同的物品,编号分别为1,2,…,n,其中第i个物品的重量为w[i],价值为v[i]。问可以往背包里面装入哪些物品,使得在不超过背包容量的前提下,背包中物品的总价值最大。
- 具体思路
1、定义状态:设dp[i][j]表示将前i个物品放入容量为j的背包中所获得的最大价值。
2、状态转移方程
当将第i个物品放入背包时,有两种情况:
- 不放入背包:此时背包容量不变,背包内的物品总价值为dp[i-1][j]。
- 放入背包:此时背包容量减少w[i],背包内的物品总价值为dp[i-1][j-w[i]]+v[i]。
因此,可以得到状态转移方程:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i])
其中,max表示取两者中的最大值。
3、初始状态
当没有物品可放入背包时,背包内的物品总价值为0,因此初始状态为:
dp[0][j] = 0
dp[i][0] = 0
4、返回结果
最终的结果为dp[n][C],即将前n个物品放入容量为C的背包中所获得的最大价值。
list.sort()易错点
print(list(cost_set).sort())
输出为None
在Python中,
list.sort()
方法是对原列表进行排序,不会返回排序后的新列表,而是直接在原列表上进行修改。如果使用list(cost_set).sort()
,会将cost_set
转换为列表并进行排序,但是并不会返回排序后的列表,而是返回None
。如果需要得到排序后的新列表,可以使用sorted()
函数
print(sorted(list(cost_set)))
Python List常用方法
sort()
方法有两个可选参数:
reverse
:如果设置为True
,则列表将按降序排序。默认为False
,即升序排序。key
:一个函数,用于从每个列表元素中提取一个比较键(例如,字符串列表中的某个子字符串)。默认值为None
,即直接比较列表元素。
# 对列表进行降序排序
numbers.sort(reverse=True)
print(numbers) # [9, 6, 5, 5, 5, 4, 3, 3, 2, 1, 1]
# 按列表元素的绝对值大小排序
numbers.sort(key=abs)
print(numbers) # [1, 1, 2, 3, 3, 4, 5, 5, 5, 6, 9]
Python Set和List对比
- Set在查找和删除元素时的性能优于List(使用哈希表实现,因此查找和删除操作速度较快。Set不支持下标索引)
- Set通常需要更少的内存空间
但是,在插入和遍历元素时,List比Set更快。
读取文件方式以节省控制台输入数据的时间
- 控制台读取方式
n, x = map(int, input().split()) # n为购物车中图书数量,x为包邮价格条件
a = [0 for _ in range(n)] # 书的价格列表(价格由低到高)
for i in range(n):
a[i] = int(input().strip())
a.sort()
- 文件读取方式
with open('temp.txt', 'r') as file:
# 逐行读取文件内容
n, x = map(int, file.readline().split()) # n为购物车中图书数量,x为包邮价格条件
a = [0 for _ in range(n)] # 书的价格列表(价格由低到高)
for i in range(n):
a[i] = list(map(int, file.readline().split()))[0]
a.sort()
file常用方法:
readline()
: 读取文件中的一行内容,包括行末的换行符。如果文件已经到达结尾,返回空字符串''
。readlines()
: 读取文件中的所有行内容,并将其作为字符串列表返回。每个列表元素包括行末的换行符。strip()
: 去掉字符串中的空白字符(包括换行符、制表符、空格等)。split()
: 将字符串按照指定分隔符分割成多个子串,并返回一个列表。如果不指定分隔符,则默认使用空白字符(包括换行符、制表符、空格等)作为分隔符。startswith()
: 判断字符串是否以指定前缀开始,返回布尔值True
或False
。endswith()
: 判断字符串是否以指定后缀结束,返回布尔值True
或False
。