CCF-CSP 202209-2-何以包邮? (Python,C++) 穷举 一维二维 动态规划 满分实现

202209-2-何以包邮?

问题

image-20230304230908735

image-20230304223002143

image-20230304231255159

思路一:类穷举,结合set集合

枚举出所有的组合的总价格,再取满足包邮的最小总价格(100分 ):

搜索发现此题多用递归,动态规划(0-1背包)实现,自己尝试用枚举(set集合结合类似穷举的方式)居然也100分了

image-20230305013430460

⚠️:关键点——set集合

  1. 书的价格列表,价格由低到高排序

  2. 用set集合优化重复的组合的总价格

  3. 遍历书的价格列表,后面组合的总价格基于前面的组合的总价格的集合

    • image-20230305014019489

实现

 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缓存
  • 倒序则不会

image-20230305150848223

⚠️:个人测试了几组正序是没问题,可通不过,有点奇怪

!!!建议用倒序的

二维数组——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)

image-20230305173505943

定义一个二维数组,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;
 }

image-20230305163857083

与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常用方法:

  1. readline(): 读取文件中的一行内容,包括行末的换行符。如果文件已经到达结尾,返回空字符串''
  2. readlines(): 读取文件中的所有行内容,并将其作为字符串列表返回。每个列表元素包括行末的换行符。
  3. strip(): 去掉字符串中的空白字符(包括换行符、制表符、空格等)。
  4. split(): 将字符串按照指定分隔符分割成多个子串,并返回一个列表。如果不指定分隔符,则默认使用空白字符(包括换行符、制表符、空格等)作为分隔符。
  5. startswith(): 判断字符串是否以指定前缀开始,返回布尔值TrueFalse
  6. endswith(): 判断字符串是否以指定后缀结束,返回布尔值TrueFalse
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值