【算法】某赛车游戏中的组合计数问题及其扩展。推导思路:层层合并

文章目录

    • 引言
    • 所有人都能完成
    • 可能有人未完成
    • 扩展问题
    • 参考资料

引言

在某款人称赛车界原神的赛车游戏中有组队竞速赛。共有n个人,n为偶数,分为人数相等的红队和蓝队进行比赛。结果按排名得分的数组为pts,单调递减且均为正整数。比如pts = [10, 8, 6, 5, 4, 3, 2, 1]表示第1~8名分别为所在队伍获得10、8、6、…、1分。总分高的队获胜,如果总分一样,则获得第一名的队获胜。对以下情况,分别求红队获胜的情况数。

  1. 所有人都能完成。
  2. 可能有人未完成(显然第一名完成了),未完成的都获得0分。

作者:hans774882968以及hans774882968以及hans774882968

本文52pojie:https://www.52pojie.cn/thread-1935160-1-1.html

本文juejin:https://juejin.cn/post/7380579040824737830

本文CSDN:https://blog.csdn.net/hans774882968/article/details/139723445

所有人都能完成

显然要么红队赢要么蓝队赢,又因为红队和蓝队地位相同,所以答案为C(n, n / 2) / 2

  1. 在下面的代码中,我还输出了所有方案,方便后文进行探究。思路:状压枚举,S为1的位表示红队队员的名次。
  2. 为了在终端输出彩色文字,我用到一个叫colorama的包,用法非常简单:参考链接1。
from colorama import Fore, init
from math import comb

pts = [10, 8, 6, 5, 4, 3, 2, 1]
bc = [0] * 256


def init_bc():
    for i in range(1, len(bc)):
        bc[i] = bc[i >> 1] + (i & 1)


def calc_teams_pt(S: int, n: int):
    red, red_rk, blue, blue_rk = 0, n + 1, 0, n + 1
    for i in range(n):
        if S >> i & 1:
            red += pts[i]
            if red_rk == n + 1:
                red_rk = i + 1
        else:
            blue += pts[i]
            if blue_rk == n + 1:
                blue_rk = i + 1
    return red, red_rk, blue, blue_rk


def solve_all_complete(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')
    tot = 0
    for S in range(1, 1 << n):
        if bc[S] != n >> 1:
            continue
        red, red_rk, blue, blue_rk = calc_teams_pt(S, n)
        if red > blue or (red == blue and red_rk < blue_rk):
            tot += 1
            colorful_pt_info = [f'{Fore.RED if S >> i & 1 else Fore.BLUE}{pts[i]}' for i in range(n)]
            print(red, red_rk, blue, blue_rk, ', '.join(colorful_pt_info))
    return tot


def main():
    init(autoreset=True)
    init_bc()
    for i in range(2, 9, 2):
        tot = solve_all_complete(i)
        print(f'tot = {tot}')
        assert tot == comb(i, i >> 1) >> 1


if __name__ == '__main__':
    main()

输出示意:

在这里插入图片描述

可能有人未完成

这个问题似乎有点难,我们不妨先输出方案。

def solve_at_most_8(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')

    def get_color(S: int, i: int, m: int):
        if i >= m:
            return Fore.WHITE
        return Fore.RED if S >> i & 1 else Fore.BLUE

    member_num = n >> 1
    tot = 0
    for m in range(n, 0, -1):
        for S in range(1, 1 << m):
            if bc[S] > member_num or m - bc[S] > member_num:
                continue
            red, red_rk, blue, blue_rk = calc_teams_pt(S, m)
            if red > blue or (red == blue and red_rk < blue_rk):
                tot += 1
                colorful_pt_info = [f'{get_color(S, i, m)}{pts[i] if i < m else 0}' for i in range(n)]
                print(red, red_rk, blue, blue_rk, ', '.join(colorful_pt_info))
    return tot

代码思路很简单,先枚举完成人数m,再进行m位,而非n位的状压枚举即可。输出:

在这里插入图片描述

n = 4时答案为3 + 3 + 2 + 1 = 9,再结合上图展示的颜色信息,似乎跟组合数息息相关。我们还是和上一章一样从对称性入手,即一种红队赢的情况反转后总是一种蓝队赢的情况。所以从直觉上看,答案应该是一些组合数的和除以2。

假设共有m人完成,1 <= m <= n,红队有0 <= i <= min(m, n / 2)人完成,那么蓝队完成人数满足0 <= m - i <= n / 2,得max(0, m - n / 2) <= i <= min(m, n / 2)i的所有取值构成一座简单的数塔,以n = 2, 4, 6, 8为例:

2 2 {1}
2 1 {0, 1}

4 4 {2}
4 3 {1, 2}
4 2 {0, 1, 2}
4 1 {0, 1}

6 6 {3}
6 5 {2, 3}
6 4 {1, 2, 3}
6 3 {0, 1, 2, 3}
6 2 {0, 1, 2}
6 1 {0, 1}

8 8 {4}
8 7 {3, 4}
8 6 {2, 3, 4}
8 5 {1, 2, 3, 4}
8 4 {0, 1, 2, 3, 4}
8 3 {0, 1, 2, 3}
8 2 {0, 1, 2}
8 1 {0, 1}

答案就是
a n s = ∑ m = 1 n ∑ i = m a x ( 0 , m − n / 2 ) m i n ( m , n / 2 ) C m i 2 ans = \frac{\sum_{m=1}^{n} \sum_{i=max(0, m - n / 2)}^{min(m, n / 2)} C_m^i}{2} ans=2m=1ni=max(0,mn/2)min(m,n/2)Cmi
去OEIS上搜这个数列,可以得到一个更简洁的公式:C(n + 1, n >> 1) - 1。接下来我们看看推导过程。首先注意到m = 1~n/2取到的i集合都是满的,于是有2^1 + ... + 2^(n/2) = 2^(n/2+1) - 2。而2^(n/2+1) = sum(C(n/2+1, i), 0 <= i <= n/2+1)。接着我们考虑看着上文展示出的数塔,结合C(i, j) = C(i - 1, j) + C(i - 1, j - 1)进行层层合并C(n/2+1, 0~n/2+1)和已有的C(n/2+1, 1~n/2)可以凑出C(n/2+2, 1~n/2+1)C(n/2+2, 1~n/2+1)和已有的C(n/2+2, 2~n/2)可以凑出C(n/2+3, 2~n/2+1)C(n/2+3, 2~n/2+1)和已有的C(n/2+3, 3~n/2)可以凑出C(n/2+4, 3~n/2+1)……直到最后只剩C(n / 2 + n / 2 + 1, n/2~n/2+1),而C(n + 1, n / 2) = C(n + 1, n / 2 + 1),于是2 * ans = 2 * C(n + 1, n / 2) - 2

完整代码:

from colorama import Fore, init
from math import comb

pts = [10, 8, 6, 5, 4, 3, 2, 1]
bc = [0] * 256


def init_bc():
    for i in range(1, len(bc)):
        bc[i] = bc[i >> 1] + (i & 1)


def calc_teams_pt(S: int, n: int):
    red, red_rk, blue, blue_rk = 0, n + 1, 0, n + 1
    for i in range(n):
        if S >> i & 1:
            red += pts[i]
            if red_rk == n + 1:
                red_rk = i + 1
        else:
            blue += pts[i]
            if blue_rk == n + 1:
                blue_rk = i + 1
    return red, red_rk, blue, blue_rk


def solve_all_complete(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')
    tot = 0
    for S in range(1, 1 << n):
        if bc[S] != n >> 1:
            continue
        red, red_rk, blue, blue_rk = calc_teams_pt(S, n)
        if red > blue or (red == blue and red_rk < blue_rk):
            tot += 1
            colorful_pt_info = [f'{Fore.RED if S >> i & 1 else Fore.BLUE}{pts[i]}' for i in range(n)]
            print(red, red_rk, blue, blue_rk, ', '.join(colorful_pt_info))
    return tot


# equivalent to max(0, m - n / 2) <= i <= min(m, n / 2)
def calc_method_num_hard(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')
    member_num = n >> 1
    tot = 0
    for m in range(n, 0, -1):
        st = set()
        for i in range(max(1, m - member_num), min(m, member_num) + 1):
            st.add(i)
            st.add(m - i)
        for v in st:
            tot += comb(m, v)
    return tot >> 1


# C(2n+1, n) - 1 = 2, 9, 34, 125
def calc_method_num_ez(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')
    return comb(n + 1, n >> 1) - 1


def solve_at_most_8(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')

    def get_color(S: int, i: int, m: int):
        if i >= m:
            return Fore.WHITE
        return Fore.RED if S >> i & 1 else Fore.BLUE

    member_num = n >> 1
    tot = 0
    for m in range(n, 0, -1):
        for S in range(1, 1 << m):
            if bc[S] > member_num or m - bc[S] > member_num:
                continue
            red, red_rk, blue, blue_rk = calc_teams_pt(S, m)
            if red > blue or (red == blue and red_rk < blue_rk):
                tot += 1
                colorful_pt_info = [f'{get_color(S, i, m)}{pts[i] if i < m else 0}' for i in range(n)]
                print(red, red_rk, blue, blue_rk, ', '.join(colorful_pt_info))
    return tot


def main():
    init(autoreset=True)
    init_bc()
    for i in range(2, 9, 2):
        tot = solve_all_complete(i)
        print(f'tot = {tot}')
        assert tot == comb(i, i >> 1) >> 1
    for i in range(2, 9, 2):
        tot1 = solve_at_most_8(i)
        print(f'tot1 = {tot1}')
        tot2 = calc_method_num_hard(i)
        tot3 = calc_method_num_ez(i)
        assert tot1 == tot2 and tot2 == tot3


if __name__ == '__main__':
    main()

扩展问题

现在考虑pts为任意单调递减数组,n为任意偶数,方案数还是C(n + 1, n >> 1) - 1吗?代码运行结果表明,答案是肯定的。

from typing import List
from math import comb
import random

bc = [0] * 1048576


def init_bc():
    for i in range(1, len(bc)):
        bc[i] = bc[i >> 1] + (i & 1)


def calc_teams_pt(S: int, n: int, pts: List[int]):
    red, red_rk, blue, blue_rk = 0, n + 1, 0, n + 1
    for i in range(n):
        if S >> i & 1:
            red += pts[i]
            if red_rk == n + 1:
                red_rk = i + 1
        else:
            blue += pts[i]
            if blue_rk == n + 1:
                blue_rk = i + 1
    return red, red_rk, blue, blue_rk


def solve(n: int, pts: List[int]):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')
    member_num = n >> 1
    tot = 0
    for m in range(n, 0, -1):
        for S in range(1, 1 << m):
            if bc[S] > member_num or m - bc[S] > member_num:
                continue
            red, red_rk, blue, blue_rk = calc_teams_pt(S, m, pts)
            if red > blue or (red == blue and red_rk < blue_rk):
                tot += 1
    return tot


def calc_method_num_hard(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')
    member_num = n >> 1
    tot = 0
    for m in range(n, 0, -1):
        st = set()
        for i in range(max(1, m - member_num), min(m, member_num) + 1):
            st.add(i)
            st.add(m - i)
        for v in st:
            tot += comb(m, v)
    return tot >> 1


# C(2n+1, n) - 1 = 2, 9, 34, 125, 461, 1715, 6434, 24309, 92377, 352715
def calc_method_num_ez(n: int):
    if n & 1:
        raise ValueError(f'n should be even, but got {n}')
    return comb(n + 1, n >> 1) - 1


def gen_decr_arr(n: int):
    a = [1]
    for _ in range(n - 1):
        a.append(a[-1] + random.randint(1, 10))
    a = a[::-1]
    return a


def main():
    init_bc()
    ans = [2, 9, 34, 125, 461, 1715, 6434, 24309, 92377, 352715]
    for i in range(2, 21, 2):
        pts1 = gen_decr_arr(i)
        print(f'pts1 = {pts1}')
        tot11 = solve(i, pts1)
        pts2 = [1 << (i - j) for j in range(i)]
        tot12 = solve(i, pts2)
        print(f'tot11 = {tot11}, tot12 = {tot12}')
        tot2 = calc_method_num_hard(i)
        tot3 = calc_method_num_ez(i)
        assert ans[(i >> 1) - 1] == tot11 and tot11 == tot12 and tot11 == tot2 and tot11 == tot3


if __name__ == '__main__':
    main()

参考资料

  1. https://www.cnblogs.com/xiao-apple36/p/9151883.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/714247.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法day28

第一题 295. 数据流的中位数 本题我们是求解给定数组的中位数。且由于需要随时给数组添加元素&#xff0c;所以我们要求解该动态数组的中位数&#xff0c;所以本题最关键的就是维护数组在添加元素之后保持有序的排序&#xff0c;这样就能很快的求解中位数&#xff1b; 解法&am…

C++11完美转发(引用折叠、万能引用)

完美转发是指在函数模板中&#xff0c;完全依照模板的参数的类型&#xff0c;将参数传递给函数模板中调用的另外一个函数。 函数模板在向其他函数传递自身形参时&#xff0c;如果相应实参是左值&#xff0c;它就应该被转发为左值&#xff1b;如果相 应实参是右值&#xff0c;它…

web安全渗透测试十大常规项(一):web渗透测试之PHP反序列化

渗透测试之XSS跨站脚本攻击 1. PHP反序列化1.1 什么是反序列化操作? - 类型转换1.2 常见PHP魔术方法?- 对象逻辑(见图)1.2.1 construct和destruct1.2.2 construct和sleep1.2.2 construct和wakeup1.2.2 INVOKE1.2.2 toString1.2.2 CALL1.2.2 get()1.2.2 set()1.2.2 isset()1…

查看npm版本异常,更新nvm版本解决问题

首先说说遇见的问题&#xff0c;基本上把nvm&#xff0c;npm的坑都排了一遍 nvm版本导致npm install报错 Unexpected token ‘.‘install和查看node版本都正确&#xff0c;结果查看npm版本时候报错 首先就是降低node版本… 可以说基本没用&#xff0c;如果要降低版本的话&…

linxu-Ubuntu系统上卸载Kubernetes-k8s

如果您想从Ubuntu系统上卸载Kubernetes集群&#xff0c;您需要执行以下步骤&#xff1a; 1.关闭Kubernetes集群&#xff1a; 如果您的集群还在运行&#xff0c;首先您需要使用kubeadm命令来安全地关闭它&#xff1a; sudo kubeadm reset在执行该命令后&#xff0c;系统会提示…

【JavaEE进阶】——利用框架完成功能全面的图书管理系统

目录 &#x1f6a9;项目所需要的技术栈 &#x1f6a9;项目准备工作 &#x1f388;环境准备 &#x1f388;数据库准备 &#x1f6a9;前后端交互分析 &#x1f388;登录 &#x1f4dd;前后端交互 &#x1f4dd;实现服务器代码 &#x1f4dd;测试前后端代码是否正确 &am…

01 - matlab m_map地学绘图工具基础函数理解(一)

01 - matlab m_map地学绘图工具基础函数理解&#xff08;一&#xff09; 0. 引言1. m_demo2. 小结 0. 引言 上篇介绍了m_map的配置过程&#xff0c;本篇开始介绍下m_map中涉及到的所有可调用函数。如果配置的没有问题&#xff0c;执行">>help m_map"可以看到类…

【C++】C++入门的杂碎知识点

思维导图大纲&#xff1a; namespac命名空间 什么是namespace命名空间namespace命名空间有什么用 什么是命名空间 namespace命名空间是一种域&#xff0c;它可以将内部的成员隔绝起来。举个例子&#xff0c;我们都知道有全局变量和局部变量&#xff0c;全局变量存在于全局域…

趣味C语言——【猜数字】小游戏

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f389;创作不易&#xff0c;请多多支持&#x1f389; &#x1f308;感谢大家的阅读、点赞、收藏和关注&#x1f495; &#x1f339;如有问题&#xff0c;欢迎指正 感谢 目录 代码…

抖音混剪素材哪里找?可以混剪搬运视频素材网站分享

在抖音上制作精彩的视频离不开高质量的素材资源。今天&#xff0c;我将为大家推荐几个优质的网站&#xff0c;帮助你解决素材短缺的问题。这些网站不仅提供丰富的素材&#xff0c;还符合百度SEO优化的规则&#xff0c;让你的视频更容易被发现。 蛙学府素材网 首先要推荐的是蛙…

模拟自动滚动并展开所有评论列表以及回复内容(如:抖音、b站等平台)

由于各大视频平台的回复内容排序不都是按照时间顺序&#xff0c;而且想看最新的评论回复讨论内容还需逐个点击展开&#xff0c;真的很蛋疼&#xff0c;尤其是热评很多的情况&#xff0c;还需要多次点击展开&#xff0c;太麻烦&#xff01; 于是写了一个自动化展开所有评论回复…

诊断解决方案——CANdesc和MICROSAR

文章目录 一、CANdesc二、MICROSAR一、CANdesc canbeded是Vector汽车电子开发软件Nun Autosar标准的工具链之一。 canbeded是以源代码的形式提供的可重用的组件,包括CAN Driver,交互层(IL),网络管理(NM),传输层(TP),诊断层(CANdesc) , 通信测量和标定协议(CCP,XCP) 和 通信控…

Es 索引查询排序分析

文章目录 概要一、Es数据存储1.1、_source1.2、stored fields 二、Doc values2.1、FieldCache2.2、DocValues 三、Fielddata四、Index sorting五、小结六、参考 概要 倒排索引 优势在于快速的查找到包含特定关键词的所有文档&#xff0c;但是排序&#xff0c;过滤、聚合等操作…

并发容器(二):Concurrent类下的ConcurrentHashMap源码级解析

并发容器-ConcurrentHashMap 前言数据结构JDK1.7版本HashEntrySegment 初始化 重要方法Put方法扩容rehash方法 前言 之前我们在集合篇里聊完了HashMap和HashTable&#xff0c;我们又学习了并发编程的基本内容&#xff0c;现在我们来聊一聊Concurrent类下的重要容器&#xff0c…

tsp可视化python

随机生成点的坐标并依据点集生成距离矩阵&#xff0c;通过点的坐标实现可视化 c代码看我的这篇文章tsp动态规划递归解法c from typing import List, Tuple import matplotlib.pyplot as plt from random import randintN: int 4 MAX: int 0x7f7f7f7fdistances: List[List[in…

最长不下降子序列LIS详解

最长不下降子序列指的是在一个数字序列中&#xff0c;找到一个最长的子序列&#xff08;可以不连续&#xff09;&#xff0c;使得这个子序列是不下降&#xff08;非递减&#xff09;的。 假如&#xff0c;现有序列A[1&#xff0c;2&#xff0c;3&#xff0c;-1&#xff0c;-2&…

60.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露(8)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;59.WEB渗透测试-信息收集- 端口、目录扫描、源码泄露&#xff08;7&#xff09; 御剑是用…

植物大战僵尸杂交版全新版v2.1解决全屏问题

文章目录 &#x1f68b;一、植物大战僵尸杂交版❤️1. 游戏介绍&#x1f4a5;2. 如何下载《植物大战僵尸杂交版》 &#x1f680;二、解决最新2.1版的全屏问题&#x1f308;三、画质增强以及减少闪退 &#x1f68b;一、植物大战僵尸杂交版 《植物大战僵尸杂交版》是一款在原版《…

【Android】三种常见的布局LinearLayout、GridLayout、RelativeLayout

【Android】三种常见的布局LinearLayout、GridLayout、RelativeLayout 在 Android 开发中&#xff0c;布局&#xff08;Layout&#xff09;是构建用户界面的基础。通过合理的布局管理&#xff0c;可以确保应用在不同设备和屏幕尺寸上都能有良好的用户体验。本文将简单介绍 And…

困惑度作为nlp指标的理解示例

为了更清晰地说明困惑度的计算过程以及如何通过困惑度判断模型的优劣&#xff0c;我们可以通过一个简单的例子来演示。假设我们有一个非常简单的文本语料库和两个基础的语言模型进行比较。 示例文本 假设我们的文本数据包括以下两个句子&#xff1a; “cat sits on the mat”…