第十三届蓝桥杯大赛软件赛省赛(Python大学A组)

2022年蓝桥杯  省赛真题
Python大学A组

        试题A:裁纸刀
        试题B:寻找整数
        试题C:质因数个数
        试题D:矩形拼接
        试题E:消除游戏
        试题F:重新排序
        试题G:全排列的价值
        试题H:最长不下降子序列
        试题I:最优清零方案
        试题J:数的拆分


试题A:裁纸刀     (5分)

【问题描述】

        小蓝有一个裁纸刀,每次可以将一张纸沿一条直线裁成两半,小蓝用一张纸打印出两行三列共6个二维码,至少使用九次裁出来,下图给出了一种裁法。

        在上面的例子中,小蓝的打印机没办法打印到边缘,所以边缘至少要裁4次。另外,小蓝每次只能裁一张纸,不能重季或者拼起来裁。如果小蓝要用一张纸打印出20行22列共440个二维码,他至少需要裁多少次?

【答案提交】

        这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只输出这个整数,输出多余的内容将无法得分。      

【解析与方法】

        一道简单的数学题 先看例子,边缘必须裁四次,然后得到两行三列共六张二维码。 横线5裁一次,竖线6 7 8 9各裁一次,加上裁边缘的四次,共九次。 也就是说,横向裁剪次数为【行数-1】次。 竖向裁剪次数为【(列数-1)*行数】次。 题目共20行22列,则次数为:4 + 19 + (21*20) = 443次。可以证明这种方法是裁剪次数最少的方法。所以当行数为20行、列数为22列的440个二维码最少需要:(20-1) + (22-1)*20   +  4   =  443

【Python程序代码】

print(20-1 + (22-1)*20 + 4)

最终结果:443


试题B:寻找整数     (5分)

【问题描述】

        有一个不超过10的17次方的正整数n,知道这个数除以2至49后的余数如下表所示,求这个正整数最小是多少?

【答案提交】

        这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只输出这个整数,输出多余的内容将无法得分。

【解析与方法】

        首先比较明显可以看出这应该属于线性同余方程组求解的问题,题目已经表明有解,所以可以直接采用中国剩余定理来求解。

        首先明确所以的质数2~49的质数ai,以及对于的余数mi.

\left\{\begin{matrix} x = a_{1}k_{1} + m_{1} & & & & & & & & & \\ x = a_{2}k_{2} + m_{2} & & & & & & & & & \end{matrix}\right.

        可以等价于:

 a_{1}k_{1} + (-a_{2})k_{2} = m_{2} - m_{1}

d = gcd(a_{1},a_{2})

        存在通解:

k_{1} = k_{1}^{'} + k\frac{a_{2}}{d}

        令通解最小且符合条件:

k_{1} = k_{1}mod\frac{a_{2}}{d}

        带入第一个式子得:

\left\{\begin{matrix} x = a_{1}(k_{1}+k\frac{a_2}{d})+m_{1}\\ x = a_{1}k_{1}+m_{1}+k\frac{a_{1}a_{2}}{d}\\ x = m_{1} + ka_{1} \end{matrix}\right.

        即可以令m1 = a1k1+m1,a1 = k(a1a2)/d,继续迭代得到最终结果。

【Python程序代码】

import math
a = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]
m = [1,2,4,4,0,10,0,18,15,16,27,22,1,11,5]
a1,m1 = a[0],m[0]
def extended_gcd(a, b):
    if b == 0:
        return a, 1, 0
    else:
        gcd, x, y = extended_gcd(b, a % b)
        return gcd, y, x - (a // b) * y
def mod(a,b):
    return ((a%b)+b)%b
for i in range(1,len(a)):
    d,k1,k2 = extended_gcd(a1,a[i])
    k1 = mod(k1*(m[i]-m1)//d,int(math.fabs(a[i]/d)))
    m1 = k1*a1 + m1
    a1 = int(math.fabs(a1//d*a[i]))
print(m1)

最终结果:2022040920220409


试题C:质因数个数     (10分) 

【题目描述】

        给定正整数n,请问有多少个质数是n的约数?

【输入格式】

        输入的第一行包含一个整数n。

【输出格式】

        输出一个整数,表示n的质数约数个数。

【样例输入】

396

【样例输出】

3

【测评用例与规模】

        对于30%的评测用例,1<n<10000
        对于60%的评测用例,1<n<10^{9}
        对于所有评测用例,1<n< 10^{18}

【解析与方法】

        根据测评用例规模可知,本体通过的话一定得是O(sqrt(n))的算法。最基本的数的分解只能通过8个点,还需要改进,可以先预处理一下2和3的因子,这样就可以直接从5开始步长设置为6,每次检验i,i+2,其中i+4不需要检测是因为初值为5,这样不管怎么样i+4都会是3的倍数,因子3已经被处理了,所以不需要。

【Python程序代码】

res = 0
n = int(input())
a = [2,3]
for i in a:
    if n%i==0:
        res += 1
        while n%i==0:n//=i
i = 5
while i*i<=n:
    a = [i,i+2]
    for j in a:
        if n%j==0:
            res += 1
            while n%j==0:
                n//=i
    i += 6
if n>1:res+=1
print(res)

试题D:矩形拼接     (10分)

【问题描述】

        已知3个矩形的大小依次是 a1 x b1,a2  b和a3 x b3。用这3个矩形能拼出的所有多边形中边数最少可以是多少?例如用3x2的矩形(用A表示)、4x1的矩形(用B表示)和2x4的矩形(用C表示)可以拼出如下4边形。

         例如用3x2的矩形(用A表示)、3x1的矩形(用B表示)和1x1的矩形(用C表示)可以拼出如下6边形。

【输入格式】

        输入包含多组数据。
        第一行包含一个整数T代表数据组数。
        以下T行,每行包含6 个整数 a1,b1,a2,b2,a3,b3,其中 a1,b1 是第一个矩形的边长,a2,b2 是第二个矩形的边长,a3,b3 是第三个矩形的边长。

【输出格式】

        对于每组数据,输出一个整数代表答案。

【样例输入】

2
2 3 4 1 2 4
1 2 3 4 5 6

【样例输出】

4
6

【测评用例与规模】

        对于10%的评测用例,1<T<5,1< a1,b1,a2,b2,a3,b3 <10,a1=a2=a3。
        对于30%的评测用例,1<T<5,1 a1,b1,a2,b2,a3,b3 <10。
        对于 60%的评测用例,1<T<10,1 a1,b1,a2,b2,a3,b3 < 20。
        对于所有评测用例,1<T<1000,1< a1,b1,a2,b2,a3,b3 < 100。

【解析与方法】

        简单观察样例思考可以发现最多8条边,如果存在两个矩形其中有一条边是相等的则为6条,如果存在三个矩形其中有一条边是相等的则是4条。如果存在两个矩形某两条边之和为第三个矩形某条边的长度则是6条,如果这两个矩形的另外一条边是相等的则是4条。根据以上结论直接暴力组合就可以。

【Python程序代码】

t = int(input())
for _ in range(t):
    res = 8
    a1,b1,a2,b2,a3,b3=map(int,input().split())
    a = [[a1,b1],[a2,b2],[a3,b3]]
    for i in range(3):
        for j in range(3):
            for k in range(3):
                if i==j or i==k or j==k:continue
                for i1 in range(2):
                    for j1 in range(2):
                        for k1 in range(2):
                            if a[i][i1]==a[j][j1]+a[k][k1]:
                                res = min(res,6)
                                if a[j][1-j1]==a[k][1-k1]:
                                    res = min(res,4)
                            if a[i][i1]==a[j][j1]:
                                res = min(res,6)
                            if a[i][i1]==a[j][j1]==a[k][k1]:
                                res = min(res,4)
    print(res)

试题E:消除游戏     (15分)

【题目描述】

        在一个字符串S中,如果Si=Si-1且Si!=Si+1则称Si和Si+1为边缘字符。如果Si!=Si-1且Si=Si+1则Si-1和Si也称为边缘字符。其它的字符都不是边缘字符。
        对于一个给定的串 S一次操作可以一次性删除该串中的所有边缘字符(操作后可能产生新的边缘字符)。
        请问经过2的64次方操作后,字符串S变成了怎样的字符串?如果结果为空则输出EMPTY。

【输入格式】

        输入一行包含一个字符串 S。

【输出格式】

       输出一行包含一个字符串表示答案,如果结果为空则输出 EMPTY。

【样例输入1】

edda

【样例输出1】

EMPTY

【样例输入2】

sdfhhhhcvhhxcxnnnnshh

【样例输出2】

s

【测评用例与规模】

        对于25%的评测用例,S<=10^{3}其中S表示S的长度。
        对于50%的评测用例,S<=10^{4}
        对于75%的评测用例,S<=10^{5}
        对于所有评测用例,S<=10^{6} ,S中仅含小写字母

【解析与方法】

        看题意是比较简单的模拟,不过问题涉及到多次删除操作,对于10^{6}的长度容易超时,所以可以选择采用双链表来处理删除操作,压缩时间复杂度。

【Python程序代码】

N = 10 ** 6 + 10  # 定义一个较大的数N,作为数组的长度
pos = []  # 用于存储需要检查的字符的位置
l, r = [0] * N, [0] * N  # l和r数组用于存储每个位置的左右位置信息
st = [False] * N  # st数组用于标记某个位置是否已经被处理过
s = '@' + input() + '@'  # 获取输入字符串,并在其首尾各添加一个'@'字符,便于处理边界情况
n = len(s) - 2  # 计算实际字符串的长度(不包括首尾的'@'字符)
# 初始化每个位置的左右位置信息
for i in range(1, n + 1):
    l[i] = i - 1
    r[i] = i + 1
# 定义一个函数,用于检查指定位置的字符是否需要处理
def check(i):
    if s[l[i]] == '@' or s[r[i]] == '@':
        return  # 如果左右位置是边界('@'字符),则不需要处理
    if s[l[i]] == s[i] and s[i] != s[r[i]]:
        pos.append(i)
        pos.append(r[i])  # 如果当前位置和其左侧位置的字符相同,但和其右侧位置的字符不同,则将当前位置和右侧位置添加到pos中
    if s[l[i]] != s[i] and s[i] == s[r[i]]:
        pos.append(l[i])
        pos.append(i)  # 如果当前位置和其左侧位置的字符不同,但和其右侧位置的字符相同,则将左侧位置和当前位置添加到pos中
# 定义一个函数,用于移除指定位置的字符
def remove(j):
    r[l[j]] = r[j]  # 将左侧位置的右侧位置更新为当前位置的右侧位置
    l[r[j]] = l[j]  # 将右侧位置的左侧位置更新为当前位置的左侧位置
    st[j] = True  # 标记当前位置已经被处理过
# 对每个位置调用check函数进行检查
for i in range(1, n + 1):
    check(i)
# 重复进行以下操作,直到pos为空:从pos中取出一个位置,如果这个位置没有被处理过,则调用remove函数移除这个位置,并将相邻的位置添加到ne数组中。然后,将ne数组中的位置作为新的待处理位置。
while pos:
    ne = []
    for p in pos:
        if st[p]:
            continue  # 如果位置已经被处理过,则跳过
        remove(p)  # 移除当前位置
        ne.append(l[p])  # 将左侧位置添加到ne中
        ne.append(r[p])  # 将右侧位置添加到ne中
    pos = []
    for e in ne:
        if not st[e]:
            check(e)  # 将ne中的位置作为新的待处理位置进行检查
# 遍历每个位置,如果这个位置没有被处理过,则将对应的字符添加到结果字符串中。
ans = ""
for i in range(1, n + 1):
    if not st[i]:
        ans += s[i]  # 将未处理位置的字符添加到结果字符串中
# 输出结果字符串或"EMPTY"
if ans:
    print(ans)  # 输出结果字符串
else:
    print("EMPTY")  # 结果字符串为空,输出"EMPTY"

试题F:重新排序     (15分)

【题目描述】

        给定一个数组A和一些查询 Li,Ri求数组中第Li至第Ri个元素之和。小蓝觉得这个问题很无聊,于是他想重新排列一下数组,使得最终每个查询结果的和尽可能地大。小蓝想知道相比原数组,所有查询结果的总和最多可以增加多少?

【输入格式】

        输入第一行包含一个整数n。
        第二行包含n个整数A1,A2,···,An,相邻两个整数之间用一个空格分隔。
        第三行包含一个整数m表示查询的数目
        接下来m行每行包含两个整数Li、 Ri相邻两个整数之间用一个空格分隔。

【输出格式】

        输出一行包含一个整数表示答案。

【样例输入】

5
1 2 3 4 5
2
1 3
2 5

【样例输出】

4

【样例说明】

        原来的和为6+14 = 20重新排列为(1,4,5,2,3)后和为 10 +14 =24增加了4。

【测评用例规模与约定】

        对于30%的评测用例,n,m<50;
        对于50%的评测用例,n,m<500;
        对于70%的评测用例,n,m<5000:
        对于所有评测用例,1<n,m<10^{5},1 < Ai < 10^{6},1< Li,Ri <10^{6}

【解析与方法】

        题意比较简单,很容易想到使用差分,把每个数需要加的次数给求出来,查询结果最大的情况则是:次数最多的乘以数值最大的。也即是差分数组的前缀和由大到小排序,和A数组由大到小排序,对应数相乘即可。由此可以解出此题。

【Python程序代码】

n = int(input())
a = [0] + list(map(int,input().split()))
m = int(input())
pre = [0]*(n+5)
for i in range(m):
    l,r = map(int,input().split())
    pre[l]+=1
    pre[r+1]-=1
for i in range(1,n+1):
    pre[i]+=pre[i-1]
ans1,ans2 = 0,0
for i in range(1,n+1):
    if pre[i]:ans1+=a[i]*pre[i]
a.sort(reverse=True)
pre.sort(reverse=True)
now = 0
for i in pre:
    if i!=0:
        ans2 += a[now]*i
        now += 1
    else:break
print(ans2-ans1)

试题G:全排列的价值     (20分)

【题目描述】

        对于一个排列 A=(a1,a2,···,an),定义价值ci为a1至ai-1中小于ai的数的个数即c_{i} = |{a_{j}}|j<i,a_{j}<a_{i}|
        定义A 的价值为\sum C_{i}
        给定n求1至n的全排列中所有排列的价值之和。

【输入格式】

        输入一行包含一个整数n。

【输出格式】

        输出一行包含一个整数表示答案,由于所有排列的价值之和可能很大,请 输出这个数除以998244353 的余数。

【样例输入1】

3

【样例输出1】

9

【样例输入2】

2022

【样例输出2】

593300958

【样例说明】

【测评用例规模与约定】

        对于40%的评测用例,n<20。
        对于70%的评测用例,n<5000。
        对于所有评测用例,2<n<10^{6}.

【解析与方法】

        首先需要明确在一个全排列中正序对的数量会等于逆序对的数量。所以可以转发为求一个全排列的数对数量。1~n的全排序有n!个情况,每个情况有n*(n-1)/2的数对。所以全排列的价值可以表示为:

value = \frac{n!*(n)*(n-1)}{4}

【Python程序与代码】

import os
import sys
n = int(input())
p = 998244353
res = ((n-1)*n/4)%p
for i in range(1,n+1):
  res = (res*i)%p
print(int(res))

试题H:最长不下降子序列     (20分)

【题目描述】

        给定一个长度为的整数序列:A1,A2,···,An 。现在你有一次机会将其中连续的K个数修改成任意一个相同值。请你计算如何修改可以使修改后的数列的最长不下降子序列最长,请输出这个最长的长度。
        最长不下降子序列是指序列中的一个子序列,子序列中的每个数不小于在它之前的数。

【输入格式】

        输入第一行包含两个整数N和K
        第二行包含N个整数A1, A2,···,An。

【输出格式】

        输出一行包含一个整数表示答案。

【样例输入】

5 1
1 4 2 8 5

【样例输出】

4

【测评用例规模与约定】

        对于20%的评测用例,1<K<N <100;
        对于30%的评测用例,1<K<N < 1000;
        对于50%的评测用例,1<K<N <10000;
        对于所有评测用例,1<K<N<10^{5} ,1 < Ai< 10^{6}.

【解析与方法】

        首先逆序单调栈 dp 出fi,其含义为以 Ai开始的最长递增子序列,最长是多少,然后顺序单调栈dpA[1,i]中的最长递增子序列,此时栈中自底向上第j个元素表示长度为j的递增子序列,最后一个元素最小可以为多少,遍历的每一个i,我们在栈中二分查找最后一个不大于 Ai+k 的元素位置j,并尝试用 fi+k + j + k 更新答案。

【Python程序代码】

import bisect  
top, s, f = 0, [0] * 100010, [0] * 100010  
n, k = map(int, input().split())  
a = [0] + list(map(int, input().split()))  
def erfens1(x, n):  
    return bisect.bisect_right(s, x, 0, n)  
  
def erfens2(x, n):  
    return bisect.bisect_left(s, x, 0, n)  
if n == k or n == 1:  
    print(n)  
else:        
    i = n  
    while i > k:  
        kk = erfens1(-a[i], top)  
        if kk == top:top += 1  
        s[kk] = -a[i]  
        f[i] = kk  
        i -= 1  
    top = 0  
    ans = 0  
    i = 1  
    while i <= n - k:  
        ans = max(ans, f[i + k] + erfens2(a[i + k], top))  
        kk = erfens1(a[i], top)  
        if kk == top:  
            top += 1  
        s[kk] = a[i]  
        i += 1  
print(max(top, ans + 1) + k)

试题I:最优清零方案     (25分)

【问题描述】

        给定一个长度为N的数列A1,A2,···,AN。现在小蓝想通过若干次操作将这个数列中每个数字清零。
        每次操作小蓝可以选择以下两种之一
            1、选择一个大于0的整数将它减去1;
            2、选择连续K个大于0的整数将它们各减去1。
        小蓝最少经过几次操作可以将整个数列清零?

【输入格式】

        输入第一行包含两个整数N和K
        第二行包含N个整数A1,A2,···,AN。

【输出格式】

        输出一个整数表示答案。

【样例输入】

4 2
1 2 3 4

【样例输出】

6

【测评用例规模与约定】

        对于20%的评测用例,1<K<N< 10。
        对于 40%的评测用例,1<K<N <100。
        对于50%的评测用例,1<K<N<1000。
        对于60%的评测用例,1<K<N<10000。
        对于70%的评测用例,1<K<N< 100000。
        对于所有评测用例,1<K<N< 1000000,0<1000000。

【解析与方法】

        直接暴力的模拟题意+一点点优化就能通过了。直接开始遍历,每次取a[i:i+k]的最小值。如果这个最小值不为0则ans加上这个最小值。同时a[i,i+k]都减去这个最小值。再遍历a[i,i+k]找到找到最后一个为0的值的下标,令i等于这个下标。最后的ans还要加上sum(a).

【Python程序代码】

import sys; readline = sys.stdin.readline
read = lambda: [int(x) for x in readline().split()]
n,k = read()
a = [0] + read()
i = 1
ans = 0
while i<=n:
    if i+k-1<=n:
        minv = min(a[i:i+k])
        if minv!=0:
            ans += minv
            for t in range(i,i+k):a[t]-=minv
        for t in range(i,i+k):
            if a[t]==0:i=t
    i += 1
ans += sum(a)
print(ans)

试题J:数的拆分     (25分)

【问题描述】

        给定T个正整数ai,分别问每个ai能否表示为x_{1}^{y1}*x_{2}^{y2} 的形式,其中x1,x2为正整数y1,y2 为大于等于2的正整数。

【输入格式】

        输入第一行包含一个整数T表示间次数。
        接下来T行每行包含一个正整数 ai。

【输出格式】

        对于每次询问,如果ai能够表示为题目描述的形式则输出 yes,否则输出no。

【样例输入】

7
2
6
12
4
8
24
72

【样例输出】

no
no
no
yes
yes
no
yes

【测评用例规模与约定】

        对于10%的评测用例,1<T<200,ai< 10^{9}
        对于30%的评测用例,1T<300,a< 10^{18}
        对于60%的评测用例,1<T<10000,ai< 10^{18}
        对于所有评测用例,1<T<100000,1< ai< 10^{18}

【解析与思路】

【Python程序代码】

not_prime = [0] * 4010
prime = []
#预处理4000以内的素数
for i in range(2, 4001):
    if not_prime[i] == 0:
        prime.append(i)
        for j in range(2 * i, 4001, i):
            not_prime[j] = 1
#判断平方数 
def square_number(x):
    y = int(x ** 0.5)
    return y * y == x or (y + 1) * (y + 1) == x
#判断立方数 
def cubic_number(x):
    y = int(x ** (1 / 3))
    return y ** 3 == x or (y + 1) ** 3 == x

T = int(input())
for i in range(T):
    a = int(input())
    #先特判平方数、立方数 
    if square_number(a) or cubic_number(a):
        print("yes")
        continue
    flag = True
    #枚举4000以内素因子
    for i in prime:
        if a % i == 0:
            mi = 0
            while a % i == 0:
                a //= i
                mi += 1
            #幂次必须大于1 
            if mi == 1:
                flag = False
                break
    if flag and (square_number(a) or cubic_number(a)):
        print("yes")
    else:
        print("no")

猜你喜欢

转载自blog.csdn.net/w2563216521/article/details/133495466