2018年第九届蓝桥杯省赛真题解题报告(Python版)-3

22 倍数问题

题目

标题:倍数问题

【题目描述】

众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。

现在小葱给了你 n 个数,希望你从这 n 个数中找到三个数,使得这三个数的和是 K 的倍数,且这个和最大。数据保证一定有解。

【输入格式】

从标准输入读入数据。

第一行包括 2 个正整数 n, K。

第二行 n 个正整数,代表给定的 n 个数。

【输出格式】

输出到标准输出。

输出一行一个整数代表所求的和。

【样例输入】

4 3
1 2 3 4

【样例输出】

9

【样例解释】

选择2、3、4。

【数据约定】

对于 30% 的数据,n <= 100。

对于 60% 的数据,n <= 1000。

对于另外 20% 的数据,K <= 10。

对于 100% 的数据,1 <= n <= 10^5, 1 <= K <= 10^3,给定的 n 个数均不超过 10^8。

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。

思路

思路:按对k取余来分组,维护每个组最大的三个值,任意两组组合(第三组的余数自然推出,因为三个组的余数之和必须是k),迭代中更新最大和

代码

if __name__ == '__main__':
    n, k = (int(x) for x in input().split(' '))
    g = [[0] * 3 for _ in range(n)]
    for num in [int(x) for x in input().split(' ')]:
        re = num % k  # 求余
        if num > g[re][0]:
            g[re][2] = g[re][1]
            g[re][1] = g[re][0]
            g[re][0] = num
        elif num > g[re][1]:
            g[re][2] = g[re][1]
            g[re][1] = num
        else:
            g[re][2] = max(g[re][2], num)
    ans = 0
    for x in range(k + 1):  #第一组
        for y in range(x, k + 1): # 第二组
            z = (k - x + k - y) % k # 第三组
            v1 = g[x][0]
            if y == x:
                v2 = g[x][1]
                if z == y:
                    v3 = g[x][2]
                else:
                    v3 = g[z][0]
            else:
                v2 = g[y][0]
                if z == x:
                    v3 = g[x][1]
                elif z == y:
                    v3 = g[y][1]
                else:
                    v3 = g[z][0]
            ans = max(ans, v1 + v2 + v3)
    print(ans)

23 小朋友崇拜圈

题目

标题:小朋友崇拜圈

班里N个小朋友,每个人都有自己最崇拜的一个小朋友(也可以是自己)。

在一个游戏中,需要小朋友坐一个圈,

每个小朋友都有自己最崇拜的小朋友在他的右手边。

求满足条件的圈最大多少人?

小朋友编号为1,2,3,…N

输入第一行,一个整数N(3

接下来一行N个整数,由空格分开。

要求输出一个整数,表示满足条件的最大圈的人数。

例如:

输入:

9
3 4 2 5 3 8 4 6 9

则程序应该输出:

4

解释:

如图p1.png所示,崇拜关系用箭头表示,红色表示不在圈中。

显然,最大圈是[2 4 5 3] 构成的圈

再例如:

输入:

30
22 28 16 6 27 21 30 1 29 10 9 14 24 11 7 2 8 5 26 4 12 3 25 18 20 19 23 17 13 15

程序应该输出:

16

资源约定:

峰值内存消耗(含虚拟机) < 256M

CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

思路

以每个元素为起点,像深搜一样去探测next,并记录每个元素的深度,如果下一个元素的深度已经存在,说明历史上已经访问过,这是圈就形成了,圈的大小可以通过深度相减得到

在过程中维护圈大小的最大值

为了提升效率,曾经访问过的元素,是不必再作为起点的

代码

from collections import defaultdict


def check(x):
    vis.add(x)  # 标记为已访问
    depth = defaultdict(int)
    while depth[data[x]] == 0: # 终止条件是下一个小朋友已经存在与路径depth中了
        _next = data[x]  # 指向的下一个元素
        depth[_next] += depth[x] + 1  # 深度比当前元素加1
        x = _next  # 替换当前元素
        vis.add(x)

    return depth[x] - depth[data[x]] + 1


if __name__ == '__main__':
    global N, data, vis
    N = int(input())
    data = [0] + [int(x) for x in input().split(" ")]
    vis = set()
    _max = 0
    for x in data[1:]:
        if x in vis: # 曾经访问过的元素不必再访问
            continue
        _max = max(_max, check(x))
    print(_max)

24 付账问题

题目

标题:付账问题

【题目描述】

几个人一起出去吃饭是常有的事。但在结帐的时候,常常会出现一些争执。

现在有 n 个人出去吃饭,他们总共消费了 S 元。其中第 i 个人带了 ai 元。幸运的是,所有人带的钱的总数是足够付账的,但现在问题来了:每个人分别要出多少钱呢?

为了公平起见,我们希望在总付钱量恰好为 S 的前提下,最后每个人付的钱的标准差最小。这里我们约定,每个人支付的钱数可以是任意非负实数,即可以不是1分钱的整数倍。你需要输出最小的标准差是多少。

标准差的介绍:标准差是多个数与它们平均数差值的平方平均数,一般用于刻画这些数之间的“偏差有多大”。

形式化地说,设第 i 个人付的钱为 bi 元,那么标准差为 : [参见p1.png]

【输入格式】

从标准输入读入数据。

第一行包含两个整数 n、S;

第二行包含 n 个非负整数 a1, …, an。

【输出格式】

输出到标准输出。

输出最小的标准差,四舍五入保留 4 位小数。

保证正确答案在加上或减去 10^−9 后不会导致四舍五入的结果发生变化。

【样例1输入】

5 2333
666 666 666 666 666

【样例输出】
0.0000

【样例解释】

每个人都出 2333/5 元,标准差为 0。

再比如:

【样例输入】

10 30
2 1 4 7 4 8 3 6 4 7

【样例输出】

0.7928

【数据说明】

对于 10% 的数据,所有 ai 相等;

对于 30% 的数据,所有非 0 的 ai 相等;

对于 60% 的数据,n ≤ 1000;

对于 80% 的数据,n ≤ 10^5;

对于所有数据,n ≤ 5 × 10^5, 0 ≤ ai ≤ 10^9。

资源约定:

峰值内存消耗(含虚拟机) < 256M

CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

思路

贪心

首先我们要知道标准差表示的是数据的波动程度,其值越大波动越大。

要使得标准差小,我们就要尽可能使得数据都比较接近平均值。

那么这题贪心策略应该是这样的:首先算出平均值s/n,把数据从小到大排序,如果某个人的钱低于该值,那么他一定是将钱全部支付,然后其余不够的其他人平摊。

但是,由于之前那个人钱不够,那么就会导致剩下人支付的平均值会增大,所以在这个平摊过程中很有可能存在某个人钱又低于这个平均值,又需要剩下的人平摊。如此反复,直到支付完成。

代码

from math import sqrt

if __name__ == '__main__':
    n, S = (int(x) for x in input().split(' '))
    data = [int(x) for x in input().split(' ')]
    ans, avg = 0.0, S / n
    data.sort()
    for i in range(n):
        if data[i] * (n - i) < S:
            ans += (avg - data[i]) ** 2
            S -= data[i]
        else:
            ans += ((S / (n - i) - avg) ** 2) * (n - i)
            break
    print('{:.4f}'.format(sqrt(ans/n)))

10 30
2 1 4 7 4 8 3 6 4 7
0.7928

25 乘积最大

题目

标题:乘积最大

给定N个整数A1, A2, … AN。请你从中选出K个数,使其乘积最大。

请你求出最大的乘积,由于乘积可能超出整型范围,你只需输出乘积除以1000000009的余数。

注意,如果X<0, 我们定义X除以1000000009的余数是负(-X)除以1000000009的余数。

即:0-((0-x) % 1000000009)

【输入格式】

第一行包含两个整数N和K。

以下N行每行一个整数Ai。

对于40%的数据,1 <= K <= N <= 100

对于60%的数据,1 <= K <= 1000

对于100%的数据,1 <= K <= N <= 100000 -100000 <= Ai <= 100000

【输出格式】

一个整数,表示答案。

【输入样例】

5 3
-100000
-10000
2
100000
10000

【输出样例】

999100009

再例如:

【输入样例】

5 3
-100000
-100000
-2
-100000
-100000

【输出样例】

-999999829

资源约定:

峰值内存消耗(含虚拟机) < 256M

CPU消耗 < 1000ms

请严格按要求输出,不要画蛇添足地打印类似:“请您输入…” 的多余内容。

思路

很麻烦的分类讨论

代码

MOD = 1000000009
maxN = 100005


def mod(x):
    if x >= 0:
        return x % MOD
    else:
        return -(-x % MOD)


def mul(a, start, end):
    '''
    将数组a中[start,end]区间内的数连乘起来
    :param a:
    :param start:
    :param end:
    :return:
    '''
    _ans = 1

    for i in a[start:end + 1]:
        _ans = mod(_ans*i)

    return _ans


def work():
    n, k = (int(x) for x in input().split(' '))
    pos, neg = [], []
    for i in range(n):
        x = int(input())
        if x > 0:
            pos.append(x)
        if x < 0:
            neg.append(x)  # 只处理正数和负数,0不处理

    pos.sort()
    neg.sort()
    '''= == == == == == 下面开始分类讨论 == == == == = '''

    sizeSum = len(pos) + len(neg)
    # 1.正数和负数的个数不足k,必然有0的存在
    if sizeSum < k:
        ans = 0
    # 2.正数和负数的个数恰好为k
    elif sizeSum == k:
        # 2.1 k与n相等,必须选这k个数,没有0
        if k == n:
            ans = mod(mul(pos, 0, len(pos) - 1) * mul(neg, 0, len(neg) - 1))
        # 2.2 k < n,有0的存在
        else:
            # 2.2.1 可得正数解当且仅当: 正数全部选中,负数全部选中且为偶数个
            if len(neg) % 2 == 0:
                ans = mod(mul(pos, 0, len(pos) - 1) * mul(neg, 0, len(neg) - 1))
            else:
                # 2.2.2 负数是奇数个,全部选中结果为负数,不全部选中可得0,结果就是0
                ans = 0
    # 3.正数和负数的个数大于k,情况比较复杂
    else:
        # sum > k
        # 3.1 没有正数, 负数和0中选k个
        if len(pos) == 0:
            if k % 2 == 0:  # 3.1.1 k是偶数
                ans = mul(neg, 0, k - 1)  # 正向选k个
            else:  # 3.1.2 k是奇数
                if sizeSum < n:  # 有0
                    ans = 0
                else:  # 没有0,必须从所有负数中选奇数个数=》连乘绝对值小的
                    ans = mul(neg, len(neg) - k, len(neg) - 1)  # 逆向选k个

        # 3.2 没有负数
        elif len(neg) == 0:
            ans = mul(pos, len(pos) - k, len(pos) - 1)  # 逆向选k个
        # 3.3 有正数也有负数
        else:
            # 3.3.1 正数不足k个
            if k >= len(pos):
                # 假定正数全部选中,剩余偶数个负数,等会再来挪动标尺
                if (k - len(pos)) % 2 == 0:
                    neg_end = k - len(pos) - 1  # 负数选择的截止下标
                    pos_start = 0  # 正数选择的起始下标
                else:
                    # 剩余个数是奇数,正数先少选一个
                    pos_start = 1  # 正数选择的起始下标
                    neg_end = k - len(pos)  # 负数选择的截止下标
            else:
                # 正数多于k个,假定从正数中先选最大的k个
                neg_end = -1
                pos_start = len(pos) - k
            # 双标尺移动
            while neg_end + 2 < len(neg) and pos_start + 2 < len(pos) and neg[neg_end + 1] * neg[neg_end + 2] > pos[
                pos_start] * pos[pos_start + 1]:
                neg_end += 2
                pos_start += 2

            ans = mod(mul(neg, 0, neg_end) * mul(pos, pos_start, len(pos) - 1))
    print(ans)


if __name__ == '__main__':
    work()

26 堆的计数

题目

标题:堆的计数

我们知道包含N个元素的堆可以看成是一棵包含N个节点的完全二叉树。

每个节点有一个权值。对于小根堆来说,父节点的权值一定小于其子节点的权值。

假设N个节点的权值分别是1~N,你能求出一共有多少种不同的小根堆吗?

例如对于N=4有如下3种:

     1
    / \
   2   3
  /
 4

     1
    / \
   3   2
  /
 4


     1
    / \
   2   4
  /
 3

由于数量可能超过整型范围,你只需要输出结果除以1000000009的余数。

【输入格式】

一个整数N。

对于40%的数据,1 <= N <= 1000

对于70%的数据,1 <= N <= 10000

对于100%的数据,1 <= N <= 100000

【输出格式】

一个整数表示答案。

【输入样例】

4

【输出样例】

3

资源约定:

峰值内存消耗(含虚拟机) < 256M

CPU消耗 < 1000ms

思路

递归过程:对于一棵限定元素个数的完全二叉树,要形成小顶堆,根节点是固定的(最小的数),节点数也是固定的:(N),左子树和右子树的size也是固定的 ==》递推式s[i]=s[2i]+s[2i+1]+1

1、从size-1中选择lsize个数组成左子树,其余组成右子树,这是一个组合数即c(size-1,lsize)

2、递归左子树得到左子树的小顶堆的数目

3、递归右子树得到右子树的小顶堆的数目

4、1,2,3的结果相乘为最终结果==》递推式dp[i]=c[size[i]-1,size[2i]]*dp[2i]*dp[2i+1]

对于固定的N,对每个节点编号,每个编号作为根的树的size,lsize是可以递推计算的(从叶子节点逆向递推,见initSize方法)==》递推式s[i]=s[2i]+s[2i+1]+1

下一步:求组合数,是三个阶乘的运算,但是要求模,除法的求模转化为求逆元==》组合n!/r!/(n-r)!,实质是求[n!/r!/(n-r)!]%MOD ==》

jie[n]*inv[r!]%MOD*inv[(n-r)!]%MOD,inv是逆元(见initJie方法)

代码


MOD = 1000000009


def c(n, r):
    '''
    组合 n!/r!/(n-r)!,实质是求[n!/r!/(n-r)!]%MOD ,需要用逆元
    即f[n]*ni[r!]%MOD*ni[(n-r)!]%MOD,ni是逆元
    :param n: 基数
    :param r: 要选择的数
    :return: c(n,r)
    '''
    return jie[n] * ni[r] % MOD * ni[n - r] % MOD


def dp():
    d = [0] * (N + 1)  # d[i]表示的是i号节点作为根,小根堆的种数
    for i in range(N, 0, -1):
        if 2 * i + 1 <= N:
            d[i] = c(size[i] - 1, size[2 * i]) * d[2 * i] % MOD * d[2 * i + 1] % MOD
        elif 2 * i <= N:
            d[i] = c(size[i] - 1, size[2 * i]) * d[2 * i] % MOD
        else:
            d[i] = 1
    print(d[1])


def init_size():
    global size
    size = [0] * (N + 1)
    for i in range(N, 0, -1):
        size[i] = (size[2 * i] if 2 * i <= N else 0) + (size[2 * i + 1] if 2 * i + 1 <= N else 0) + 1


def _pow(a, n):
    '''
    快速幂运算
    :param a:
    :param n:
    :return:
    '''
    if a == 0 or a == 1:
        return a
    ans = 1
    x = a
    while n > 0:
        if n & 1:
            ans = ans * x % MOD
        n >>= 1
        x = x * x % MOD
    return ans


def init_jie():
    global jie, ni
    jie = [0] * (N + 1)
    ni = [0] * (N + 1)
    jie[0], ni[0] = 1, 1
    for i in range(1, N + 1):
        jie[i] = jie[i - 1] * i % MOD
        ni[i] = _pow(jie[i], MOD - 2)  # 根据费马小定理,m是质数时,a关于m的逆元就是a的(m-2)次方


if __name__ == '__main__':
    global N
    N = int(input())
    init_size()
    init_jie()
    dp()

27 耐摔指数优化

优化思路

转换问题为,m部手机允许测试t次,能支持h层高的测试 也就是能测出耐摔指数的上限是多少(无论什么情况)
2部手机的最佳策略为:
从t层仍(对,t=允许测试总次数),如果是好的,下次从 t + (t-1)层仍,以此类推,即向上层高的增量为剩余次数
递推式:ff(t) = f(t-1)+1+ff(t-1)
3部手机的最佳策略为:
从2部手机t-1次所支持层高+1处开始仍,如果是好的,下次增高2部手机t-2次支持的层高,以此类推
递推式:fff(t)=ff(t-1)+1+fff(t-1)

代码

if __name__ == '__main__':
    N = int(input())
    hh, hhh = [0], [0]
    hh.append(1)
    hhh.append(1)
    i = 1
    while hh[i] < N:
        i += 1
        hh.append(hh[i - 1] + i)  # hh[i]=h[i-1]+1+hh[i-1] = hh[i-1] + i
    i = 1
    while hhh[i] < N:
        i += 1
        hhh.append(hh[i - 1] + 1 + hhh[i - 1])  # hhh[i]=hh[i-1]+1+hhh[i-1]

    print(i)

你可能感兴趣的