2020第十一届蓝桥杯Python组国赛【真题+解析+代码】

news/2024/12/2 11:30:21/

🎁2020第十一届蓝桥杯python组国赛真题



🚀 真题练习,冲刺国赛 🚀

在这里插入图片描述

2020年第十一届蓝桥python组国赛真题+解析+代码

博观而约取,厚积而薄发


🏆国赛真题目录

文章目录

  • 🎁2020第十一届蓝桥杯python组国赛真题
    • 试题A.美丽的2⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题B.合数个数⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题C.阶乘约数⭐️⭐️⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题D.本质上升序列⭐️⭐️⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题E.玩具蛇⭐️⭐️⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题F.天干地支⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题G.重复字符串⭐️⭐️⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题H.答疑⭐️⭐️⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题I.补给⭐️⭐️⭐️⭐️⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现
    • 试题J.蓝跳跳⭐️⭐️⭐️⭐️⭐️⭐️
      • 🍰1.题目
      • 👑2.思路分析
      • 💯3.代码实现


试题A.美丽的2⭐️

🍰1.题目

美丽的2
在这里插入图片描述

👑2.思路分析

难度:⭐️

标签:枚举&字符串

🎇思路:暴力法

🔱思路分析:

本题为填空题,只需要遍历 1 → 2020 1→2020 12020即可:

  1. 将数字转为字符串 ( s t r ) (str) (str)
  2. 如果 ′ 2 ′ '2' 2 在字符串 s s s中,则结果 + 1 +1 +1

💯3.代码实现

cnt=0
for i in range(1,2021):s=str(i)if '2' in s:cnt+=1
print(cnt) # 563

输出结果:
在这里插入图片描述



试题B.合数个数⭐️

🍰1.题目

合数个数
在这里插入图片描述

👑2.思路分析

难度:⭐️

标签:枚举

🎇思路①:试除法

🔱思路分析:

本题为填空题,因此可以直接使用暴力法得出结果

  1. 由于 1 1 1 2 2 2均为素数,所以只需遍历 3 → 2020 3→2020 32020
  2. 当遍历到一个数时,我们对其进行判断:
    2 → x 2→\sqrt{x} 2x 进行试除
    ②如果整除,则说明有除了 1 1 1和本身外的其他因子存在,为合数;否则,为素数
  3. 返回值为 1 1 1,则为合数, c n t + 1 cnt+1 cnt+1


🎇思路②:素数筛

🔱思路分析:

对于任意一个素数,它的正整数倍 ( ≥ 2 ) (≥2) (2)一定是合数

  1. 构造 v i s vis vis数组,先初始化 v i s vis vis T r u e True True T r u e True True为素数, F a l s e False False为合数)
  2. 遍历 2 → 2020 2→2020 22020,若当前的数 x x x T r u e True True(未被标记),则为素数,对他进行处理:
    遍历 x x x的倍数 i = 2 x , 3 x , . . . i=2x,3x,... i=2x,3x,...
    ②如果 i i i未被标记过(防止合数重复标记,如2的3倍为6,3的2倍也为6,则统计的合数个数会增多),则标记vis[i]=False,并让 c n t + 1 cnt+1 cnt+1

图解:
在这里插入图片描述


💯3.代码实现

1.试除法实现

from math import sqrtdef not_prime(x):for i in range(2,int(sqrt(x))+1):if x%i==0:return 1 # 为合数return 0 # 为素数cnt=0
for x in range(3,2021):if not_prime(x):cnt+=1
print(cnt) # 合数个数:1713

2.素数筛实现

vis=[True]*2021 # 默认都是素数
cnt=0
for x in range(2,2021):if vis[x]: # 如果是素数(没有被标记过)for i in range(x+x,2021,x): # 将[x,2020]中素数x的倍数全部标记为合数if vis[i]==True:cnt+=1vis[i]=False
print(cnt) # 1713

输出结果:

在这里插入图片描述



试题C.阶乘约数⭐️⭐️⭐️

🍰1.题目

阶乘约数

在这里插入图片描述

👑2.思路分析

难度:⭐️⭐️⭐️

标签:数论——唯一分解定理

🎇思路①:暴力法

🔱思路分析:

要得到所有的因子,不难想到用暴力法统计,但是!!不出意外地超时了

step

  1. 遍历 i = 1 → 100 ! i=1→\sqrt{100!} i=1100! ,不断试除
  2. 如果能被整除,我们对该数进行讨论:
    ①如果 i = 100 ! i=\sqrt{100!} i=100! ,则这里因子只增加了一个,cnt+=1
    ②如果 i i i为其他数,则这里因子必然有两个,cnt+=2

不通过代码:

from math import sqrt
num=1
for i in range(2,101):num*=icnt=0
for j in range(1,int(sqrt(num))+1):if num%j==0:if j==int(sqrt(num)):cnt+=1else:cnt+=2
print(cnt)


🎇思路②:唯一分解定理

🔱思路分析:

我们先来介绍一个与此题相关的数学定理:

🚀 整数的唯一分解定理:

在这里插入图片描述

即正整数n(>1)可分解为: n = p 1 α 1 ∗ p 2 α 2 ∗ . . . ∗ p k α k n=p_1^{α1}*p_2^{α2}*...*p_k^{αk} n=p1α1p2α2...pkαk

其中, p 1 < p 2 < … < p k p1<p2<…<pk p1<p2<<pk皆素数, α i ( i = 1 , 2 , … , k ) αi (i=1,2,…,k) αi(i=12k)皆为正整数,而正整数n的正因数个数为: n u m = ( α 1 + 1 ) ∗ ( α 2 + 1 ) ∗ . . . ∗ ( α k + 1 ) num=(α1+1)*(α2+1)*...*(αk+1) num=(α1+1)(α2+1)...(αk+1),也就是将素数的次方加1后求乘积

为什么?

因为对于 p 1 p_1 p1来说,它的指数为 α 1 α1 α1,则分解时它可以有 α 1 + 1 α1+1 α1+1种取法;同理, α 2 α2 α2 α 2 + 1 α2+1 α2+1种取法;…;所以,总的因数个数为 ( α 1 + 1 ) ∗ ( α 2 + 1 ) ∗ . . . ∗ ( α k + 1 ) (α1+1)*(α2+1)*...*(αk+1) (α1+1)(α2+1)...(αk+1)


什么意思呢?

举个例子:

我们要求100的正因数个数:

  1. 将100分解为: 100 = 2 2 ∗ 5 2 100=2^2*5^2 100=2252
  2. p 1 = 2 , α 1 = 2 , p 2 = 5 , α 2 = 2 p_1=2,α1=2,p_2=5,α2=2 p1=2α1=2p2=5α2=2,所以正因数的个数为: ( 2 + 1 ) ∗ ( 2 + 1 ) = 9 (2+1)*(2+1)=9 (2+1)(2+1)=9
  3. 9 9 9个数即为: 1 , 2 , 4 , 5 , 10 , 20 , 25 , 50 , 100 {1,2,4,5,10,20,25,50,100} 1,2,4,5,10,20,25,50,100

有了上面的定理支撑,我们就可以用代码实现了:

step

  1. 由于 n ! = 1 ∗ 2 ∗ . . . ∗ n n!=1*2*...*n n!=12...n,所以,我们先找到 [ 1 , n ] [1,n] [1,n]中的质数,记录在数组 s s s
  2. 质因数分解:将每一个数都分解为质数的乘积,用字典记录 s s s中每一个素数的得到次数,最终即为指 α i αi αi
  3. 最后用公式: ( α 1 + 1 ) ∗ ( α 2 + 1 ) ∗ . . . ∗ ( α k + 1 ) (α1+1)*(α2+1)*...*(αk+1) (α1+1)(α2+1)...(αk+1)即可


💯3.代码实现

唯一分解定理实现:

from math import *def is_prime(x):for i in range(2,int(sqrt(x))+1):if x%i==0:return 0 # 为合数return 1 # 为素数s=[2] # 记录[1,100]的素数
for i in range(3,101):if is_prime(i):s.append(i)p=dict()
for i in s:p[i]=0 # 将质数的次数初始化为0# 质因数分解
for i in range(1,101):for a in s:while i%a==0:i=i/ap[a]+=1# 求正因数个数
res=1
for d in p.values():res*=(d+1)
print(res)

输出结果:

在这里插入图片描述



试题D.本质上升序列⭐️⭐️⭐️

🍰1.题目

本质上升序列

在这里插入图片描述


👑2.思路分析

难度:⭐️⭐️⭐️

标签:dp动态规划

🎇思路:线性dp

🔱思路分析:

要求升序字符串,即字符串中从左到右满足线性关系(后一个大于前一个),因此,我们可以用动态规划从前到后不断统计个数

step

  1. 字符串为 s s s s [ k ] s[k] s[k]即代表字符串中第 k + 1 k+1 k+1个字符
  2. 动态规划数组 d p [ i ] dp[i] dp[i] d p [ i ] dp[i] dp[i]表示以字符 s [ i ] s[i] s[i]结尾的满足本质上升序列的字符串的个数,其中 i ∈ [ 1 , n − 1 ] i∈[1,n-1] i[1,n1],初始化 d p [ ] = 1 dp[]=1 dp[]=1
  3. 遍历 i i i 1 1 1 n − 1 n-1 n1,对于每一个 i i i,我们遍历i之前的所有字符 s [ j ] s[j] s[j]
    ①如果当前 s [ i ] > s [ j ] s[i]>s[j] s[i]>s[j]:则表明,以 s [ j ] s[j] s[j]结尾的满足本质上升序列的字符串,在结尾加上 s [ i ] s[i] s[i]后,也满足升序条件,且为一种新的字符串情况,因此:dp[i]+=dp[j]
    ②如果当前 s [ i ] = s [ j ] s[i]=s[j] s[i]=s[j]:则表明,对于 s [ j ] s[j] s[j]之前的字符,以 s [ j ] s[j] s[j]结尾和 s [ i ] s[i] s[i]结尾的本质是一样的,只是换了尾字符的位置,所以,要减去这些重复的情况:dp[i]-=dp[j]
    ③如果当前 s [ i ] < s [ j ] s[i]<s[j] s[i]<s[j]:则表明,不能直接向以 s [ j ] s[j] s[j]结尾的字符串后添加 s [ i ] s[i] s[i],因为不满足升序条件了,所以跳过,继续判断下一个 s [ j ] s[j] s[j]continue
  4. 最后,统计 d p dp dp数组 [ 0 , n − 1 ] [0,n-1] [0,n1]的和,即为所有情况

为什么要初始化 d p dp dp 1 1 1

  1. d p [ 0 ] dp[0] dp[0]需要:因为以s[0](第一个字符)结尾的情况必定有 1 1 1种,且后续 d p [ 1 → n ] dp[1→n] dp[1n]的值都是由 d p [ 0 ] → d p [ i − 1 ] dp[0]→dp[i-1] dp[0]dp[i1]递推而来的,所以初始化 d p [ 0 ] = 1 dp[0]=1 dp[0]=1
  2. 默认以 s [ i ] s[i] s[i]结尾的满足条件的字符串都是一种情况(也就是单个字符的情况):我们在遍历时,只遍历了 s [ i ] s[i] s[i]之前的 s [ 0 → ( i − 1 ) ] s[0→(i-1)] s[0(i1)]的情况,而没有考虑单个 s [ i ] s[i] s[i]自身,所以,要初始化为1,如果字符串 s s s中有单个重复的情况,也会在 s [ i ] = s [ j ] s[i]=s[j] s[i]=s[j]时被减去

以字符串 ′ l a n q i a o ′ 'lanqiao' lanqiao为例:
在这里插入图片描述

在这里插入图片描述

特别注意,当 s [ 5 ] = s [ 1 ] = ′ a ′ s[5]=s[1]='a' s[5]=s[1]=a时,应该执行dp[5]-dp[1],这里可以直观地看到,就是减去’a’这一情况,避免了重复字符串的出现

在这里插入图片描述

在这里插入图片描述

以此类推,最终相加即可得到总数为: 1 + 1 + 3 + 6 + 2 + 0 + 8 = 21 1+1+3+6+2+0+8=21 1+1+3+6+2+0+8=21


💯3.代码实现

线性dp实现:

s='tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhfiadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqijgihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmadvrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl'
n=len(s)
dp=[1]*n # dp[i]表示以s[i]结尾的字符串中本质上升序列的数目(单个字符都满足)
for i in range(1,n):for j in range(i): # [0,i-1]if s[i]>s[j]: # 如果s[i]>s[j],则可以看做以s[j]结尾的字符串之后再添加一个s[i]便可以得到新的一种本质上升序列dp[i]+=dp[j]elif s[i]==s[j]: # 如果s[i]=s[j],则表示dp[j]的情况和dp[i]部分重复了,所以要减去这些情况dp[i]-=dp[j]else:continue # 如果s[i]<s[j],继续下一轮
print(sum(dp)) # 3616159

输出结果:

在这里插入图片描述



试题E.玩具蛇⭐️⭐️⭐️

🍰1.题目

玩具蛇

在这里插入图片描述

👑2.思路分析

难度:⭐️⭐️⭐️

标签:dfs深度优先遍历

🎇思路:dfs深搜

🔱思路分析:

题目要求找到所有摆放蛇各个身段的情况,由于蛇身是连续的,也就是相邻的身段只能朝上下左右摆放,不能是对角线,所以,我们可以对蛇头(序号为 1 1 1)在每一个方格处都进行 d f s dfs dfs深搜操作,最终得到所有满足条件的情况


注意:对于相同形状,但存在在相同方格中,蛇的位置不同时,为不同的方案

图示:
在这里插入图片描述

step

  1. 4 x 4 4x4 4x4方格中每一个格子都作为一次蛇头的位置(序号为1)进行 d f s dfs dfs
  2. d f s ( x , y , s t e p ) dfs(x,y,step) dfs(x,y,step):从蛇头出发,表示当前蛇尾所在位置为 ( x , y ) (x,y) (x,y),且蛇的身长为 s t e p step step
    ①结束条件:如果蛇的身长达到16 step=16,则表示该方案可行,cnt+=1
    ②深搜四个方向:如果蛇尾向该方向扩展后,仍在4x4方格内,且该方格未被标记过时: 标记 + 深搜 + 回溯 标记+深搜+回溯 标记+深搜+回溯
  3. 最后 c n t cnt cnt的值即为情况总数

注意:在 4 x 4 4x4 4x4方格中,每一轮以新的方格作为蛇头时,要重新构造 v i s [ 4 ] [ 4 ] vis[4][4] vis[4][4]数组,因为要清空之前 d f s dfs dfs留下的标记


💯3.代码实现

dfs实现:

def judge(x,y):if x>=0 and x<=3 and y>=0 and y<=3 and vis[x][y]==False: # 如果该点还未被访问过return Truereturn Falsedef dfs(x,y,step):global cnt,vis# 结束条件if step==16:cnt+=1returnfor d in [(1,0),(-1,0),(0,1),(0,-1)]: # 对四个方向进行深搜nx,ny=x+d[0],y+d[1]if judge(nx,ny):# 标记vis[nx][ny]=True# dfsdfs(nx,ny,step+1)# 回溯vis[nx][ny]=Falsecnt = 0
for i in range(4):for j in range(4):vis = [[False] * 4 for _ in range(4)]  # 存4x4方格图(注意:每次到一个新的点,必须要清空vis数组,重新dfs)vis[i][j] = True # 对当前点(蛇头)进行标记dfs(i,j,1)  # 对点(i,j)进行深搜,1表示:经过了(i,j)位置,为第一步
print(cnt)

输出结果:
在这里插入图片描述



试题F.天干地支⭐️

🍰1.题目

天干地支

在这里插入图片描述


👑2.思路分析

难度:⭐️

标签:列表

🎇思路:模拟

🔱思路分析:

本题要求任意年份的天干地支 ( [ 1 , 9999 ] ) ([1,9999]) ([1,9999]),由于天干的周期为 10 10 10,地支的周期为 12 12 12,因此,我们可以根据周期的差异分别计算天干和地支

step

  1. 构造天干和地支的列表
    这里有一个小技巧 t i p s tips tips:我们可以先算出公元 0 0 0年的天干地支 → → 为庚申年
    于是,我们可以将天干地支列表的首元素变为 ′ g e n g ′ 'geng' geng ′ s h e n ′ 'shen' shen,之后则按照正常顺序放置,这样,我们就可以实现 年份除以相应周期的余数 = = =对应列表的下标
  2. 年份分别对 10 10 10 12 12 12取余:t=year%10;d=year%12
    再将对应列表下标中的元素取出,拼在一起即为结果

💯3.代码实现

列表操作实现:

Tiangan=['geng','xin','ren','gui','jia','yi','bing','ding','wu','ji']
Dizhi=['shen','you','xu','hai','zi','chou','yin','mou','chen','si','wu','wei']
year=int(input())
t=year%10
d=year%12
print(Tiangan[t]+Dizhi[d])

输出结果:

在这里插入图片描述



试题G.重复字符串⭐️⭐️⭐️

🍰1.题目

重复字符串

在这里插入图片描述


👑2.思路分析

难度:⭐️⭐️⭐️

标签:枚举

🎇思路:字符串分组

🔱思路分析:

要令字符串 s = s= s=某字符串 s ′ s' s重复 k k k次,且满足对字符串 s s s 的修改次数最少,因此,我们可以将字符串 s s s分成连续的 k k k 个子串,每一个子串的大小为 L = l e n ( s ) / / k L=len(s)//k L=len(s)//k (如果有余数则无解),我们对这 k k k个子串对齐摆放,按列处理即可

step

  1. 先将字符串 s s s 切片处理,也就是取长度为 L = l e n ( s ) / / k L=len(s)//k L=len(s)//k 的子串,如果有余数 l e n ( s ) % k ! = 0 len(s)\%k!=0 len(s)%k!=0,则无解;反之,则继续进行下一步

  2. 由于新字符串的长度为 L L L,也就是:
    [ 0 , l e n ( s ) − 1 ] [0,len(s)-1] [0,len(s)1]分成 [ 0 , L − 1 ] , [ L , 2 L − 1 ] , . . . , [ l e n ( s ) − L , l e n ( s ) − 1 ] [0,L-1],[L,2L-1],...,[len(s)-L,len(s)-1] [0,L1][L,2L1]...[len(s)L,len(s)1]


    如何得到这样一个 k k k x L L L 阶的矩阵呢?
    可以依次以 s [ 0 → ( L − 1 ) ] s[0→(L-1)] s[0(L1)]为起始, L L L为步长遍历整个字符串并取出对应位置的元素:即可得到一列的元素


  3. 我们按列进行处理,每得到一列的元素,我们将其放入字典中,判断哪一个字符出现的次数最多(为 n u m num num,则我们将出现次数少的其他元素全部改为该元素即可满足在该位置上修改的次数最少,其中,每一列至少需要修改的次数为: c n t = k − n u m cnt=k-num cnt=knum


    因为总共有 k k k行,所以减去包含次数最多元素的 n u m num num行后,剩下的 k − n u m k-num knum行需要修改

  4. 循环遍历 0 → L − 1 0→L-1 0L1列,统计每一列至少需要修改次数之和即为 r e s res res


图解:

e . g e.g e.g 以字符串 s = ′ a b c a b a b c a c a a ′ s='abcababcacaa' s=abcababcacaa 为例,假设 k = 4 k=4 k=4,也就是重复出现4次

我们可以得到: k = 4 , L = 12 / / 4 = 3 k=4,L=12//4=3 k=4L=12//4=3,按照上述方法,则字符串 s s s 分组为 4 4 4 x 3 3 3 阶矩阵
在这里插入图片描述

我们对每一列进行字典统计操作,统计出现次数最多的字符和对应的次数
在这里插入图片描述

所以,至少需要修改: 2 + 2 + 1 = 5 2+2+1=5 2+2+1=5 次,即将其变为 ′ a b a ′ 'aba' aba 重复 4 4 4次得到 s s s


💯3.代码实现

字符串分组实现:

k=int(input())
s=input()
res=0
if len(s)%k!=0: # 不能被整除print(-1) # 无解
else:l=len(s)//k # l是列数for i in range(l):  # 分别取首位置为[0,l-1]dic = {}  # 清空字典for j in range(i,len(s),l): # 以l为步长 构造同一列下的字符集字典dic[s[j]]=dic.get(s[j],0)+1val=dic.values()num=max(val) # 找到同一列中出现次数最多的数res+=k-num # k是行数 k-num即为要修改的最小数字个数print(res)

输出结果:

在这里插入图片描述



试题H.答疑⭐️⭐️⭐️

🍰1.题目

答疑
在这里插入图片描述


👑2.思路分析

难度:⭐️⭐️⭐️

标签:贪心

🎇思路:贪心算法

🔱思路分析:

首先,由贪心算法可知,我们只需要让答疑消耗总时间越短的人越先答疑,即可实现发送消息的时刻之和最短

在这里插入图片描述

step

  1. 将发送消息前后所需要的时间视为两个部分:即 x i → 发消息 → y i x_i → 发消息 → y_i xi发消息yi,总时间为 z i = x i + y i z_i=x_i+y_i zi=xi+yi,嵌套列表存放每一个人的 [ x i , y i , z i ] [x_i,y_i,z_i] [xi,yi,zi]
  2. 依据 z i z_i zi进行从小到大的排序,当前同学发消息的时刻为 t i m e + x i time+x_i time+xi,遍历所有嵌套列表,每一轮循环结束后,都让时刻处于当前同学完全结束的时刻,也就是 t i m e = z 0 + z 1 + . . . + z i time=z_0+z_1+...+z_i time=z0+z1+...+zi,之后继续下一个同学的答疑
  3. 最终,时刻之和即为 r e s res res

证明:

现在我们来证明,为什么这道题可以用贪心算法:

d e f def def:首先,每个人的 s i s_i si a i a_i ai 都可以视为一个部分(也就是前期准备),记为 d d d;收拾时间即为 e e e

规定 P i P_i Pi 表示第 i i i 个人, x i x_i xi y i y_i yi分别对应了第 i i i个人的 d d d e e e z i = x i + y i z_i=x_i+y_i zi=xi+yi 对应第 i i i个人的答疑总时间

假设当前序列为 P 1 P 2 . . . P i P i + 1 . . . P n P_1P_2...P_iP_{i+1}...P_n P1P2...PiPi+1...Pn,令 j = i + 1 j=i+1 j=i+1

易得,交换 P i P_i Pi P j P_j Pj 顺序并不会影响除了二者之外的发送消息时间,所以交换后变化的只有二者的发送时间,而影响最终结果

假设除了 i , j i,j i,j 之外的人的发送时间之和为 S S S,则 r e s = S + P i 发送时间 + P j 发送时间 res=S+P_i发送时间+P_j发送时间 res=S+Pi发送时间+Pj发送时间

x 1 + y 1 + x 2 + y 2 + . . . + x i − 1 + y i − 1 x_1+y_1+x_2+y_2+...+x_{i-1}+y_{i-1} x1+y1+x2+y2+...+xi1+yi1 S i − 1 S_{i-1} Si1

①交换顺序前 P i P_i Pi 的发送时间为 S i − 1 + x i S_{i-1}+x_i Si1+xi P j P_j Pj的发送时间为 S i − 1 + x i + y i + x j S_{i-1}+x_i+y_i+x_j Si1+xi+yi+xj r e s = S + ( S i − 1 + x i ) + ( S i − 1 + x i + y i + x j ) res=S+(S_{i-1}+x_i)+(S_{i-1}+x_i+y_i+x_j) res=S+(Si1+xi)+(Si1+xi+yi+xj)
②交换顺序后 P i P_i Pi 的发送时间为 S i − 1 + x j + y j + x i S_{i-1}+x_j+y_j+x_i Si1+xj+yj+xi P j P_j Pj 的发送时间为 S i − 1 + x j S_{i-1}+x_j Si1+xj r e s ′ = S + ( S i − 1 + x j + y j + x i ) + ( S i − 1 + x j ) res'=S+(S_{i-1}+x_j+y_j+x_i)+(S_{i-1}+x_j) res=S+(Si1+xj+yj+xi)+(Si1+xj)

对比 r e s res res r e s ′ res' res,代入 z i , z j z_i,z_j zi,zj 后可以看出:

r e s = S + 2 S i − 1 + x i + x j + res=S+2S_{i-1}+x_i+x_j+ res=S+2Si1+xi+xj+ z j z_j zj
r e s ′ = S + 2 S i − 1 + x i + x j + res'=S+2S_{i-1}+x_i+x_j+ res=S+2Si1+xi+xj+ z i z_i zi

所以,影响结果的只有每个人答疑花费的总时间 ( z i z_i zi z j z_j zj),因此,只需要将总时间小的放在前面进行答疑即可(即对 z z z 进行排序)


💯3.代码实现

贪心算法实现:

n=int(input())
li=[]
for i in range(n):x,y,z=map(int,input().split())li.append([x+y,z,x+y+z])
li.sort(key=lambda x:x[2])  # 根据总时间排序,短的放前面
time=0
res=0
for i in range(n):time+=li[i][0] # 加上前期准备时间res+=timetime+=li[i][1] # 加上离开时间
print(res)

输出结果:

在这里插入图片描述



试题I.补给⭐️⭐️⭐️⭐️⭐️

🍰1.题目

补给
在这里插入图片描述


👑2.思路分析

难度:⭐️⭐️⭐️⭐️⭐️

标签: 状压 d p + F l o y d 状压dp+Floyd 状压dp+Floyd算法

🎇思路:状态压缩 d p + F l o y d dp+Floyd dp+Floyd算法

🔱思路分析:

本题为经典的"旅行商"问题:从起点出发,找一条经过所有n个点再回到起点的最短路径长度,于是,我们可以建模为带权无向图


step

1. F l o y d Floyd Floyd算法:

F l o y d Floyd Floyd算法思想是:由小图逐渐扩展为大图,不断向已有点的集合中加入一个新的点,依次判断该点的加入是否会对集合中的点之间的最短路径产生影响

  1. 用邻接矩阵 w [ i ] [ j ] w[i][j] w[i][j]储存路径:表示从点 i i i到点 j j j的最短路径
    初始时: w [ i ] [ j ] w[i][j] w[i][j]表示点 i i i到点 j j j之间的直接路径长度
    结束时: w [ i ] [ j ] w[i][j] w[i][j]表示点 i i i到点 j j j之间的最短路径长度


    注意:
    ①没有直接相连的两点默认值为无穷大
    w[i][i]=0


  1. 将第 1 1 1 n n n个点依次加入点集 S S S中,对图中每一个点对 ( i , j ) (i,j) i,j进行遍历,其中 i ∈ [ 1 , n ] , j ∈ [ 1 , n ] i∈[1,n],j∈[1,n] i[1,n],j[1,n],如果 w [ i ] [ j ] > w [ i ] [ k ] + w [ k ] [ j ] w[i][j]>w[i][k]+w[k][j] w[i][j]w[i][k]+w[k][j],也就是 i → k → j i→k→j ikj的距离比 i i i直接到 j j j的距离小,于是更新 i i i j j j 的最短路径 w [ i ] [ j ] w[i][j] w[i][j]

  2. 至此,我们就得到了无向图中任意两点间的最短距离


代码实现 F l o y d Floyd Floyd

def Floyd():global wfor i in range(n): # 点0到n-1for j in range(n): # 表示点i到点jfor k in range(n): # 引入第三点kw[i][j]=min(w[i][j],w[i][k]+w[k][j])

如图所示:

在这里插入图片描述


2. 状态压缩 d p dp dp

对于上述问题,也就是给定一张n个点的带权无向图,点为 0 0 0 ~ n − 1 n-1 n1,现在已知任意两个点的最短路径,要求起点0到达每一个点后再回来的最短路径长度,也就是 0 0 0 ~ n − 1 n-1 n1个点都要经过

①确认状态:

d p dp dp动态规划:不难发现,我们只需要知道:①哪些点已经走过;②目前停在那个点上 即可,而此时不用关心已经遍历过的点之间的具体顺序,因为我们已经得到了经过当前点集 S S S中的点的最短路径


于是,我们设状态为 S S S d p [ S ] [ j ] dp[S][j] dp[S][j]表示起点 0 0 0到点 j j j,且经过点的点集 S S S 时的最短路径长度

则有 d p [ S ] [ j ] = d p [ S − j ] [ k ] + w [ k ] [ j ] dp[S][j]=dp[S-j][k]+w[k][j] dp[S][j]=dp[Sj][k]+w[k][j],其中 S − j S-j Sj表示点集 S S S中去除掉点 j j j后且包含点 k k k的集合, w [ k ] [ j ] w[k][j] w[k][j] 即为已经求得的点 k k k 到点 j j j 的最短路径长度

所以,当前新加入的点为 j j j,我们依次遍历 S − j S-j Sj中的点 k k k,找到 S − j S-j Sj j j j最短的一条路,再更新 d p [ S ] [ j ] dp[S][j] dp[S][j]的值即可,以此类推, S S S 则由 0 0 0 逐渐扩展到 n − 1 n-1 n1


图解算法:

在这里插入图片描述


②状态压缩 d p dp dp

那么,我们应该如何表示这个集合 S S S呢?

这里就是状态压缩 d p dp dp的核心:用二进制数 0 / 1 0/1 0/1表示是否经过该点, 1 1 1表示经过, 0 0 0表示未经过

01101 01101 01101,表示经过了点 0 0 0 2 2 2 3 3 3,此时点集为: S = 0 , 2 , 3 S={0,2,3} S=0,2,3

所以, [ 1 , ( 1 < < n ) − 1 ] [1,(1<<n)-1] [1,(1<<n)1] 为每一个状态的点集 S S S ,最终为 111...1 ( n 个 1 ) 111...1 (n个1) 111...1(n1),表示所有点都经过了


  1. 初始化:dp[1][0]=0,表示只有起点时,到起点的最短距离自然为 0 0 0


  2. 对每个状态下的 S S S,遍历所有点 [ 0 , n − 1 ] [0,n-1] [0,n1],记为 j j j,判断当前 j j j 是否在当前点集 S S S 中:
    ①如果 j j j不在:即S>>j & 1==0 (按位与 ‘&’)
    则继续找下一个在集合中的 j j j 点;
    ②如果 j j j:即S>>j & 1!=0
    则找到没有点 j j j 时的集合 S − j S-j Sj,遍历 S − j S-j Sj 中的所有点 k k k,即满足:S^(1<<j)>>k &1 !=0 (按位异或 ‘^’)
    从而找到一个k,得到一条最短路径 w [ k ] [ j ] w[k][j] w[k][j],则有:dp[S][j]=dp[S-j][k]+w[k][j]


代码实现:

dp=[[float('inf')]*21 for _ in range(1<<21)]
dp[1][0]=0
for S in range(1,1<<n):for j in range(n):if S>>j & 1!=0: # 找到在集合S中的点jfor k in range(n):if S^(1<<j)>>k &1 !=0: # 找到在集合S-j中的点kdp[S][j]=min(dp[S][j],dp[S^(1<<j)][k]+w[k][j])

  1. 最后,找到经过所有点时的最佳停留点 i ( i ≠ 0 ) i(i≠0) i(i=0),使得此时走过的距离dp[(1<<n)-1][i],加上点 i i i 到起点 0 0 0的距离w[i][0]最短
res=float('inf')
for i in range(1,n):res=min(res,dp[(1<<n)-1][i]+w[i][0]) # 最短距离=min(到最终停留点i的距离+i到0的距离)

💯3.代码实现

状压 d p dp dp+ F l o y d Floyd Floyd算法实现:

from math import *
def dis(a,b):d=sqrt(pow(a[0]-b[0],2)+pow(a[1]-b[1],2))return ddef Floyd():global wfor i in range(n): # 点0到n-1for j in range(n): # 表示点i到点jfor k in range(n): # 引入第三点kw[i][j]=min(w[i][j],w[i][k]+w[k][j])n,D=map(int,input().split())
w=[[float('inf')]*n for _ in range(n)] # 邻接矩阵记录最短路径
xy=[] # 记录坐标点
for i in range(n):xy.append(list(map(float,input().split())))# 最短路径
for i in range(0,n):for j in range(i+1,n):d=dis(xy[i],xy[j]) # 传入两点的坐标if d<=D: # 如果两点的距离不超过最大飞行距离 更新w[i][j] = dw[j][i] = d
Floyd()# 状态压缩
dp=[[float('inf')]*n for _ in range(1<<n)]
dp[1][0]=0
for S in range(1,1<<n):for j in range(n):if S>>j & 1!=0: # 找到在集合S中的点jfor k in range(n):if S^(1<<j)>>k &1 !=0: # 找到在集合S-j中的点kdp[S][j]=min(dp[S][j],dp[S^(1<<j)][k]+w[k][j])res=float('inf')
for i in range(1,n):res=min(res,dp[(1<<n)-1][i]+w[i][0]) # 最短距离=min(到最终停留点i的距离+i到0的距离)
print("%.2f"%res)

输出结果:(python本身运行较慢,只能通过60%)

在这里插入图片描述



试题J.蓝跳跳⭐️⭐️⭐️⭐️⭐️⭐️

🍰1.题目

蓝跳跳

在这里插入图片描述


👑2.思路分析

难度:⭐️⭐️⭐️⭐️⭐️⭐️

标签: d p dp dp动态规划 +矩阵快速幂

🎇思路: d p dp dp +矩阵快速幂

🔱思路分析:

本题要求方案总数,因此考虑用 d p dp dp或者 d f s dfs dfs解决

此时会存在两个问题:

d p [ L ] [ k ] dp[L][k] dp[L][k],需要的空间很大( L = 1 0 18 L=10^{18} L=1018
L L L 太大,会超时

但不要着急,我们来一一解决


step

1.动态规划 d p dp dp

我们考虑直接 d p dp dp

定义 d p [ i ] [ j ] dp[i][j] dp[i][j] i i i表示当前跳到的位置, i i i 1 1 1递推到 L L L j j j 则表示走到 i i i 位置时最后一次走的步长为 j j j

则状态转移方程为:

d p [ i ] [ j ] = { ∑ d = 1 k d p [ i − j ] [ d ] j < p ∑ d = 1 p − 1 d p [ i − j ] [ d ] j ≥ p dp[i][j]=\begin{cases} ∑_{d=1}^{k}dp[i-j][d] & j<p \\ \\ ∑_{d=1}^{p-1}dp[i-j][d] & j≥p \end{cases} dp[i][j]= d=1kdp[ij][d]d=1p1dp[ij][d]j<pjp


也就是说,如果到位置 i i i 时:

  1. 最后一步的步长 j < p j<p j<p:那么可以选择 [ 1 , k ] [1,k] [1,k] 中的任意一个距离跳跃到 i − j i-j ij 位置,因为对于当前状态下来说,无论如何都不可能存在连续两次跳跃距离 > p >p >p
  2. 最后一步的步长 j ≥ p j≥p jp:那么此时只能选择 [ 1 , p − 1 ] [1,p-1] [1,p1] 中的一个距离跳跃到 i − j i-j ij 位置,因为这一步超过了 p p p,那么前一步就一定不能超过 p p p

此时的时间复杂度即为: O ( k p L ) O(kpL) O(kpL)

最终结果为: r e s = ∑ d = 1 k d p [ L ] [ d ] res=∑_{d=1}^kdp[L][d] res=d=1kdp[L][d]


接下来进行状态压缩<空间优化>:不难发现,对于 j j j,只有 < p <p <p ≥ p ≥p p 两种状态,我们不需要直到 j j j 的具体值,因此,将状态 j j j 压缩为 0 / 1 0/1 0/1,于是:

定义 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1],表示当前跳到位置 i i i 0 / 1 0/1 0/1表示最后一步是否小于 p p p 的计数

状态转移方程变为:

d p [ i ] [ d ] = { ∑ j = 1 p − 1 d p [ i − j ] [ 0 ] + d p [ i − j ] [ 1 ] d = 0 ∑ j = p k d p [ i − j ] [ 0 ] d = 1 dp[i][d]=\begin{cases} ∑_{j=1}^{p-1}dp[i-j][0] + dp[i-j][1] & d=0 \\ \\ ∑_{j=p}^{k}dp[i-j][0] & d=1 \end{cases} dp[i][d]= j=1p1dp[ij][0]+dp[ij][1]j=pkdp[ij][0]d=0d=1


此时的时间复杂度为: O ( k L ) O(kL) O(kL)

最终结果为: r e s = d p [ L ] [ 0 ] + d p [ L ] [ 1 ] res=dp[L][0]+dp[L][1] res=dp[L][0]+dp[L][1]


2.矩阵快速幂

由上分析,我们发现,其状态转移 O ( k L ) O(kL) O(kL) 是线性的,也就是说,对于当前状态 i i i,只和前 k k k 个状态有关,且可以写成矩阵乘法的形式,具体地,我们写成 2 k 2k 2k x 2 k 2k 2k 矩阵

例如,当 k = 3 , p = 2 k=3,p=2 k=3p=2 时:


我们先列出线性方程组:

{ d p [ i ] [ 1 ] = d p [ i − 2 ] [ 0 ] + d p [ i − 3 ] [ 0 ] d p [ i ] [ 0 ] = d p [ i − 1 ] [ 1 ] + d p [ i − 1 ] [ 0 ] d p [ i − 1 ] [ 1 ] = d p [ i − 1 ] [ 1 ] d p [ i − 1 ] [ 0 ] = d p [ i − 1 ] [ 0 ] d p [ i − 2 ] [ 1 ] = d p [ i − 2 ] [ 1 ] d p [ i − 2 ] [ 0 ] = d p [ i − 2 ] [ 0 ] \begin{cases} \ dp[i][1]=dp[i-2][0]+dp[i-3][0] \\ \ dp[i][0]=dp[i-1][1]+dp[i-1][0] \\ \ dp[i-1][1]=dp[i-1][1] \\ \ dp[i-1][0]=dp[i-1][0] \\ \ dp[i-2][1]=dp[i-2][1] \\ \ dp[i-2][0]=dp[i-2][0] \\ \end{cases}  dp[i][1]=dp[i2][0]+dp[i3][0] dp[i][0]=dp[i1][1]+dp[i1][0] dp[i1][1]=dp[i1][1] dp[i1][0]=dp[i1][0] dp[i2][1]=dp[i2][1] dp[i2][0]=dp[i2][0]


再表示为对应的矩阵乘法,也就实现了状态转移:

也就是由状态 i − 1 i-1 i1 扩展到了状态 i i i


( d p [ i ] [ 1 ] d p [ i ] [ 0 ] d p [ i − 1 ] [ 1 ] d p [ i − 1 ] [ 0 ] d p [ i − 2 ] [ 1 ] d p [ i − 2 ] [ 0 ] ) = ( 0 0 0 1 0 1 1 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 ) ( d p [ i − 1 ] [ 1 ] d p [ i − 1 ] [ 0 ] d p [ i − 2 ] [ 1 ] d p [ i − 2 ] [ 0 ] d p [ i − 3 ] [ 1 ] d p [ i − 3 ] [ 0 ] ) \left( \begin{matrix} dp[i][1] \\ dp[i][0] \\ dp[i-1][1] \\ dp[i-1][0] \\ dp[i-2][1] \\ dp[i-2][0] \end{matrix} \right) = \left( \begin{matrix} 0 & 0 & 0 & 1 & 0 & 1\\ 1 & 1 & 0 & 0 & 0 & 0\\ 1 & 0 & 0 & 0 & 0 & 0\\ 0 & 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 0 & 0 & 0\\ 0 & 0 & 0 & 1 & 0 & 0 \end{matrix} \right) \left( \begin{matrix} dp[i-1][1] \\ dp[i-1][0] \\ dp[i-2][1] \\ dp[i-2][0] \\ dp[i-3][1] \\ dp[i-3][0] \\ \end{matrix} \right) dp[i][1]dp[i][0]dp[i1][1]dp[i1][0]dp[i2][1]dp[i2][0] = 011000010100000010100001000000100000 dp[i1][1]dp[i1][0]dp[i2][1]dp[i2][0]dp[i3][1]dp[i3][0]


此时,时间复杂度为: O ( 8 k 3 l o g L ) O(8k^3logL) O(8k3logL)

(这里还没有做出来,先挖个坑吧…)


💯3.代码实现

动态规划 d p dp dp 实现:

k,p,L=map(int,input().split())
dp=[[0]*2 for _ in range(L+1)]
dp[0][0]=1
for i in range(1,L+1):# 1.考虑步长小于p时for j in range(1,p):if i-j<0:breakdp[i][0]+=(dp[i-j][0]+dp[i-j][1])%20201114# 2.考虑步长大于等于p时for j in range(p,k+1):if i-j<0:breakdp[i][1]+=dp[i-j][0]%20201114res=sum(dp[L])%20201114
print(res)

输出结果:

在这里插入图片描述


🎇觉得有帮助的话,就赏个三连吧~🎇

如有错误,欢迎指正~!

在这里插入图片描述


http://www.ppmy.cn/news/133587.html

相关文章

尼康图像处理软件——nx studio

就算你不是专业的摄影师&#xff0c;那么相信你对尼康品牌也并不陌生吧&#xff0c;而近日该品牌为大家推出了nx studio软件&#xff0c;这是一款便捷使用的电脑图像软件&#xff0c;旨在为用户提供强大的后期影像增强的功能。当然一个相机品牌的厂商出品了图像处理软件也没什么…

尼康介绍

尼康 开放分类&#xff1a; 日本、 公司、 相机、 数码、 品牌 尼康公司 尼康公司历史悠久&#xff0c;创建于1917年7月25日&#xff0c;由 三菱财 团&#xff08;Mitsubishi&#xff09;投资&#xff0c;合并三个光学仪器厂组成。当时&#xff0c;主要为日本国防部生产军用光…

realsense D400系列自校准

Viewer工具与校准纹理目标纸下载&#xff1a;https://download.csdn.net/download/weixin_43042683/86242590 1.下载最新版本的Viewer工具和固件 官网下载地址&#xff1a;https://www.intelrealsense.com/sdk-2/ 2.打开Intel RealSense Viewer&#xff0c;连接设备&#xff…

新建VS工程配置Intel Realsense D400系列相机(D435相机为例)

今天尝试了一下在新的Visual Studio工程下配置Intel Realsense D400系列相机的library&#xff0c;在朋友的协助下很顺利的一次性成功了&#xff0c;拿出来和大家分享一下。配置的过程主要参考的是之前配置OpenCV时候的方法。 硬件平台&#xff1a; win10 x64 Visual Stud…

Intel-RealSense-D400-Series的halcon接口/工具包配置说明

提示&#xff1a;intel realsense D400系列的halcon接口配置 文章目录 前言一、下载地址二、配置过程 1.将工具包放入对应的目录2.将dll文件放入到halcon根目录三、连接效果图及代码 前言 intel realsense D400系列的halcon接口配置 一、下载地址 连接&#xff1a;https://ww…

Intel RealSense实感深度摄像头自校准(Self-Calibration)步骤详细,D400系列适用

喜提国庆8天工作乐&#xff0c;改代码真的很帅&#xff0c;才华皆一切&#xff0c;这篇博客的由来是因为我做实验了&#xff0c;然后摄像头的有效距离贼差&#xff0c;打了技术人员的电话说他们的有效距离4m&#xff0c;然后边缘相差为百分之2&#xff0c;简直离谱&#xff0c;…

Intel英特尔RealSense实感深度摄像头 自校准(Self-Calibration)操作步骤讲解 D400系列适用

B站参考视频&#xff1a;Intel英特尔RealSense实感深度摄像头 自校准&#xff08;Self-Calibration&#xff09;操作步骤讲解 D400系列适用_ Viewer工具与校准纹理目标纸下载&#xff1a;https://download.csdn.net/download/weixin_43042683/86242590 1.下载最新版本的Viewe…

尼康 Z 400mm f / 2.8 TC VR S 评测

尼康表示&#xff0c;尼克尔 Z 400mm f / 2.8 TC VR S 是第一款尼克尔 Z 长焦定焦镜头&#xff0c;属于追求高级光学性能的尼克尔 Z 镜头的 S-Line&#xff08;S-型&#xff09;。它通过快速的 f / 2.8 最大光圈特有的美丽虚化以及高分辨率&#xff0c;实现逼真影像的渲染。此外…