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

文章目录

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

引言

在某款人称赛车界原神的赛车游戏中有组队竞速赛。共有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 combpts = [10, 8, 6, 5, 4, 3, 2, 1]
bc = [0] * 256def 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 + 1for i in range(n):if S >> i & 1:red += pts[i]if red_rk == n + 1:red_rk = i + 1else:blue += pts[i]if blue_rk == n + 1:blue_rk = i + 1return red, red_rk, blue, blue_rkdef solve_all_complete(n: int):if n & 1:raise ValueError(f'n should be even, but got {n}')tot = 0for S in range(1, 1 << n):if bc[S] != n >> 1:continuered, red_rk, blue, blue_rk = calc_teams_pt(S, n)if red > blue or (red == blue and red_rk < blue_rk):tot += 1colorful_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 totdef 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) >> 1if __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.WHITEreturn Fore.RED if S >> i & 1 else Fore.BLUEmember_num = n >> 1tot = 0for m in range(n, 0, -1):for S in range(1, 1 << m):if bc[S] > member_num or m - bc[S] > member_num:continuered, red_rk, blue, blue_rk = calc_teams_pt(S, m)if red > blue or (red == blue and red_rk < blue_rk):tot += 1colorful_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 combpts = [10, 8, 6, 5, 4, 3, 2, 1]
bc = [0] * 256def 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 + 1for i in range(n):if S >> i & 1:red += pts[i]if red_rk == n + 1:red_rk = i + 1else:blue += pts[i]if blue_rk == n + 1:blue_rk = i + 1return red, red_rk, blue, blue_rkdef solve_all_complete(n: int):if n & 1:raise ValueError(f'n should be even, but got {n}')tot = 0for S in range(1, 1 << n):if bc[S] != n >> 1:continuered, red_rk, blue, blue_rk = calc_teams_pt(S, n)if red > blue or (red == blue and red_rk < blue_rk):tot += 1colorful_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 >> 1tot = 0for 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) - 1def 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.WHITEreturn Fore.RED if S >> i & 1 else Fore.BLUEmember_num = n >> 1tot = 0for m in range(n, 0, -1):for S in range(1, 1 << m):if bc[S] > member_num or m - bc[S] > member_num:continuered, red_rk, blue, blue_rk = calc_teams_pt(S, m)if red > blue or (red == blue and red_rk < blue_rk):tot += 1colorful_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 totdef 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) >> 1for 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 == tot3if __name__ == '__main__':main()

扩展问题

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

from typing import List
from math import comb
import randombc = [0] * 1048576def 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 + 1for i in range(n):if S >> i & 1:red += pts[i]if red_rk == n + 1:red_rk = i + 1else:blue += pts[i]if blue_rk == n + 1:blue_rk = i + 1return red, red_rk, blue, blue_rkdef solve(n: int, pts: List[int]):if n & 1:raise ValueError(f'n should be even, but got {n}')member_num = n >> 1tot = 0for m in range(n, 0, -1):for S in range(1, 1 << m):if bc[S] > member_num or m - bc[S] > member_num:continuered, red_rk, blue, blue_rk = calc_teams_pt(S, m, pts)if red > blue or (red == blue and red_rk < blue_rk):tot += 1return totdef calc_method_num_hard(n: int):if n & 1:raise ValueError(f'n should be even, but got {n}')member_num = n >> 1tot = 0for 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) - 1def gen_decr_arr(n: int):a = [1]for _ in range(n - 1):a.append(a[-1] + random.randint(1, 10))a = a[::-1]return adef 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 == tot3if __name__ == '__main__':main()

参考资料

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【LeetCode】LCR 124. 推理二叉树

题目链接&#xff1a; 题目描述&#xff1a;某二叉树的先序遍历结果记录于整数数组 preorder&#xff0c;它的中序遍历结果记录于整数数组 inorder。请根据 preorder 和 inorder 的提示构造出这棵二叉树并返回其根节点。 注意&#xff1a;preorder 和 inorder 中均不含重复数字…

Stable Diffusion3 开源!一文教你玩转 Stable Diffusion3

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 汇总合集…

[C++]使用yolov10的onnx模型结合onnxruntime和bytetrack实现目标追踪

【官方框架地址】 yolov10yolov10框架&#xff1a;https://github.com/THU-MIG/yolov10 bytetrack框架&#xff1a;https://github.com/ifzhang/ByteTrack 【算法介绍】 Yolov10与ByTetrack&#xff1a;目标追踪的强大组合 Yolov10和ByTetrack是两种在目标追踪领域具有显…

OceanBase 金融项目优化案例

领导让我帮忙支持下其他项目的SQL优化工作&#xff0c;呦西&#xff0c;是收集案例的好机会。&#x1f60d; 下面SQL都是在不能远程的情况下&#xff0c;按照原SQL的逻辑等价改写完成发给现场同学验证。 案例一 慢SQL&#xff0c;4.32秒&#xff1a; SELECT MY_.*, RM FROM (SE…

C语言:链表

链表 介绍单向链表节点结构创建节点插入节点删除节点遍历链表尾部插入查找节点链表反转示例程序程序1程序2 介绍 链表是一种常见的数据结构&#xff0c;用于存储一系列线性数据。与数组不同&#xff0c;链表中的元素在内存中不必是连续存放的&#xff0c;而是通过指针将每个元…

Jackson的使用

一引入依赖 <!--Jackson是spring-boot-starter-json的一个依赖&#xff08;spring-boot-starter-web中包含spring-boot-starter-json&#xff09;。也就是说&#xff0c;当项目中引入spring-boot-starter-web后会自动引入spring-boot-starter-json --> <dependency&g…

4款好用的文本扩展器!!提高工作效率!【送源码】

今天的文章中为大家带来几款好用的文本扩展器&#xff0c;帮助大家提供工作效率&#xff0c;减少重复劳动&#xff5e; Beeftext Beeftext 是一个文本扩展工具&#xff0c;可以帮助用户快速输入短语、段落或者常用的文本片段。它允许你创建自定义的缩写和对应的文本替换&…

使用tkinter创建带有图标的菜单栏

使用tkinter创建带有图标的菜单栏 效果代码代码解析创建主窗口加载图标创建菜单栏添加文件菜单添加带图标的菜单项 Tkinter 的默认菜单外观较为简单&#xff0c;可以通过自定义和添加图标&#xff0c;让菜单显示更好看。 效果 代码 import tkinter as tk from tkinter import …

课时154:项目发布_手工发布_手工发布

1.2.3 手工发布 学习目标 这一节&#xff0c;我们从 基础知识、简单实践、小结 三个方面来学习 基础知识 简介 为了合理的演示生产环境的项目代码发布&#xff0c;同时又兼顾实际实验环境的资源&#xff0c;我们这里将 B主机和C主机 用一台VM主机来实现&#xff0c;A主机单…

Arduino入门2——常用函数及用法

Arduino入门2——串口驱动函数及用法 IO串口 上期&#xff0c;我们简单的认识了一下Arduino&#xff0c;浅浅的入了个门&#xff0c;这一期我们介绍以下Arduino串口常用的函数及用法 IO 常用串口库函数如下&#xff1a; 函数名用法及解析pinMode()用于IO口初始化digitalWrite…

2024050802-重学 Java 设计模式《实战模板模式》

重学 Java 设计模式&#xff1a;实战模版模式「模拟爬虫各类电商商品&#xff0c;生成营销推广海报场景」 一、前言 黎明前的坚守&#xff0c;的住吗&#xff1f; 有人举过这样一个例子&#xff0c;先给你张北大的录取通知书&#xff0c;但要求你每天5点起床&#xff0c;12点…

Proteus 新建工程

Proteus 新建工程 新建简单工程 首先在File工具栏中点击New Project&#xff0c;弹出新建工程向导程序(New Proteus Wizard) 填写工程名称与存储路径&#xff0c;选择New Proteus并点击Next进行下一步设置 我们不需要生成PCB文件&#xff0c;一路默认&#xff0c;点击Next即…

实战计算机网络02——物理层

实战计算机网络02——物理层 1、物理层实现的功能2、数据与信号2.1 数据通信模型2.2 通信领域常用术语2.3 模拟信号和数字信号 3、信道和调制3.1 信道3.2 单工通信、半双工通信、全双工通信3.3 调制3.4 奈式准则3.5 香农定律 4、传输媒体4.1 导向传输媒体4.2 非导向传输媒体 5、…

高级人工智能复习 题目整理 中科大

题目整理 填空 1.准确性&#xff0c;复杂性&#xff0c;验证集 2. 3 2 n 3^{2^n} 32n 3 C 2 n m 3^{C^m_{2n}} 3C2nm​ 3 m 3^m 3m n 1 n1 n1 3. 状态 从状态s采取行动a后继续采用策略 π \pi π的收益 环境 4. 语法 语义 推理规则 5. 参与者&#xff0c;策略集&#xff…

Elasticsearch 8.1官网文档梳理 - 十一、Ingest pipelines(管道)

Ingest pipelines 管道&#xff08;Ingest pipelines&#xff09;可让让数据在写入前进行常见的转换。例如可以利用管道删除文档&#xff08;doc&#xff09;的字段、或从文本中提取数据、丰富文档&#xff08;doc&#xff09;的字段等其他操作。 管道&#xff08;Ingest pip…

VirtualBox、Centos7下安装docker后pull镜像问题、ftp上传文件问题

Docker安装篇(CentOS7安装)_docker 安装 centos7-CSDN博客 首先&#xff0c;安装docker可以根据这篇文章进行安装&#xff0c;安装完之后&#xff0c;我们就需要去通过docker拉取相关的服务镜像&#xff0c;然后安装相应的服务容器&#xff0c;比如我们通过docker来安装mysql,…

vue 使用 ztree 超大量数据,前端树形结构展示

ztree 是一个很经典的基于jquey开发的树结构编辑展示UI组件库。 创建一个文件 ztree.vue&#xff0c;代码如下&#xff1a; <template><div><div class"ztree vue-giant-tree" :id"ztreeId"></div><div class"treeBox&q…

Termius安装docker

安装Termius 直接上官网 新建主机 更新一下yum 更新完成 安装docker的包 直接用命令安装 设置一下开机启动&#xff0c;可以查看docker的版本

Ui学习--UITableView

UI学习 UITableView基础UITableView协议UITableView高级协议与单元格总结 UITableView基础 UITableView作为iOS中的一个控件&#xff0c;用于以表格形式展示数据。例如通讯录好友&#xff0c;朋友圈信息等&#xff0c;都是UITableView的实际运用场景。 首先我们先要加入两个协…

K210使用雷龙NAND完成火灾检测

NAND 文章目录 NAND前言一、NAND是什么&#xff1f;二、来看一看NAND三、部署火灾检测 前言 前几天收到了雷龙NAND的芯片&#xff0c;一共两个芯片和一个转接板&#xff0c;我之前也没有使用过这款芯片&#xff0c;比较好奇&#xff0c;体验了一下&#xff0c;个人认为&#x…