抽牌游戏

news/2024/11/14 11:08:44/文章来源:https://www.cnblogs.com/XuYueming/p/18545218

题意简述

一副 \(n+m\) 张牌的扑克牌,\(m\) 张 joker。初始牌堆里有这样一副牌。随机抽一张牌拿走,如果是 joker,将所有牌放回牌堆并打乱。问你抽到过所有 \(n\) 张正常牌的期望抽牌次数是多少?对 \(M = 19260817\) 取模。

\(n \leq 10^8\)\(m \leq 10^{18}\)

题目分析

概率期望类题目,考虑 DP,并且期望 DP 套路是从后往前递推。

显然应该可以状压 DP,但是其非常不利于后续优化。所以尝试使用线性 DP。

DP 记录值显然是当前状态到终态需要的期望抽牌次数。状态有哪些呢?牌堆、抽到过哪些牌了。如果朴素记录就是状压了,但是我们发现,\(n\) 张普通牌和 \(m\) 张 joker 并没有本质差别,是等价的,无非前者需要区分有没有被抽到过。

不妨使用 \(f_{i,j}\) 表示当前已经抽到过 \(i\) 张牌,牌堆里有 \(j\) 张牌,到终态的期望抽牌次数。我们需要明确的是,哪些状态是合法的,显然需要 \(j\) 中包含 \(m\) 张牌,和 \(n-i\)\(i\) 中没有的普通牌,即 \(j \geq n+m-i\)。对于 \(i+j \geq n+m\) 的情况表示 \(i+j-n-m\) 张牌已经抽到过了,但后来被重新加入牌堆中。

明确好状态,就可以转移了。我们有 \(\frac{n-i}{j}\) 的概率,抽到一张全新的牌,转移到 \(f_{i+1,j-1}\);有 \(\frac{i+j-n-m}{j}\) 的概率,抽到一张抽到过的牌,转移到 \(f_{i,j-1}\);有 \(\frac{m}{j}\) 的概率,抽到 joker,转移到 \(f_{i,n+m}\)。验证一下,\(\frac{n-i}{j}+\frac{i+j-n-m}{j}+\frac{m}{j}=1\),没有问题。

\[\Large f_{i,j}={\textstyle \frac{n-i}{j}}f_{i+1,j-1}+{\textstyle \frac{i+j-n-m}{j}}f_{i,j-1}+{\textstyle \frac{m}{j}}f_{i,n+m}+1 \]

边界 \(f_{n,j}=0\),答案 \(f_{0,n+m}\)。这不好递推,怎么办呢?

我们可以把它看做二维平面内的随机游走,向左下、左、行末行走。这个往行末行走就很经典。我们可以设 \(f_{i,j}=k_{i,j}\cdot f_{i,n+m}+b_{i,j}\),从 \(j=n+m-i\) 推到 \(j=n+m\),就是一个方程,方程解出来,\(f_{i}\) 就解出来了。具体可以见文末代码。

上述 DP 时空复杂度 \(\Theta(n^2)\),需要优化。经过打表发现,\(f_{i}\)\(j\) 为等差数列。

【404 not found】

作者太菜了,还不会证。

我们设 \(f_{i,j}=\lambda_i+\mu_i\cdot(n+m-j)\),我们只需要任意两项 \(j\),就能确定 \(\lambda_i, \mu_i\),也就确定了 \(f_{i}\),为了方便起见,取末两项解方程。

\[\begin{aligned} &\Large\left\{\begin{aligned} & f_{i,n+m} = {\textstyle \frac{n-i}{n+m}}f_{i+1,n+m-1}+{\textstyle\frac{i}{n+m}}f_{i,n+m-1}+{\textstyle\frac{m}{n+m}}f_{i,n+m}+1 \\ & f_{i,n+m-1}= {\textstyle \frac{n-i}{n+m-1}}f_{i+1,n+m-2}+{\textstyle\frac{i-1}{n+m-1}}f_{i,n+m-2}+{\textstyle\frac{m}{n+m-1}}f_{i,n+m}+1 \end{aligned}\right. \\\\ \Large\Rightarrow&\Large\left\{\begin{aligned} & \lambda_i = {\textstyle \frac{n-i}{n+m}}(\lambda_{i+1}+\mu_{i+1})+{\textstyle\frac{i}{n+m}}(\lambda_i+\mu_i)+{\textstyle\frac{m}{n+m}}\lambda_i+1 \\ & \lambda_i+\mu_i= {\textstyle \frac{n-i}{n+m-1}}(\lambda_{i+1}+2\mu_{i+1})+{\textstyle\frac{i-1}{n+m-1}}(\lambda_i+2\mu_i)+{\textstyle\frac{m}{n+m-1}}\lambda_i+1 \end{aligned}\right. \\\\ \Large\Rightarrow&\Large\left\{\begin{aligned} & \lambda_i={\textstyle\frac{i}{n-i}}\mu_i+\lambda_{i+1}+\mu_{i+1}+{\textstyle\frac{n+m}{n-i}} \\ & {\normalsize(n+m-2i+1)}\mu_i={\normalsize(i-n)}\lambda_i+{\normalsize n+m-1}+{\normalsize(n-i)}(\lambda_{i+1}+2\mu_{i+1}) \end{aligned}\right. \\\\ \Large\Rightarrow&\Large\left\{\begin{aligned} & \lambda_i={\normalsize\frac{i\cdot\mu_i}{n-i}+\lambda_{i+1}+\mu_{i+1}+\frac{n+m}{n-i}} \\ & \mu_i={\normalsize\frac{(n-i)\cdot\mu_{i+1}-1}{n+m-i+1}} \end{aligned}\right. \\\\ \end{aligned} \]

于是可以 \(\mathcal{O}(n \log M)\),若 \(m = \mathcal{O}(n)\),则可以完全线性 \(\mathcal{O}(n)\)。边界 \(\lambda_n=\mu_n=0\),答案 \(\lambda_0\)

代码

取模板子
namespace Mod_Int_Class {template <typename T, typename _Tp>constexpr bool in_range(_Tp val) {return std::numeric_limits<T>::min() <= val && val <= std::numeric_limits<T>::max();}template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>static constexpr inline bool is_prime(_Tp val) {if (val < 2) return false;for (_Tp i = 2; i * i <= val; ++i)if (val % i == 0)return false;return true;}template <auto _mod = 19260817, typename T = int, typename S = long long>class Mod_Int {static_assert(in_range<T>(_mod), "mod must in the range of type T.");static_assert(std::is_integral<T>::value, "type T must be an integer.");static_assert(std::is_integral<S>::value, "type S must be an integer.");public:constexpr Mod_Int() noexcept = default;template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>constexpr Mod_Int(_Tp v) noexcept: val(0) {if (0 <= S(v) && S(v) < mod) val = v;else val = (S(v) % mod + mod) % mod;}constexpr T const& raw() const {return this -> val;}static constexpr T mod = _mod;template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>constexpr friend Mod_Int pow(Mod_Int a, _Tp p) {return a ^ p;}constexpr friend Mod_Int sub(Mod_Int a, Mod_Int b) {return a - b;}constexpr friend Mod_Int& tosub(Mod_Int& a, Mod_Int b) {return a -= b;}constexpr friend Mod_Int add(Mod_Int a) { return a; }template <typename... args_t>constexpr friend Mod_Int add(Mod_Int a, args_t... args) {return a + add(args...);}constexpr friend Mod_Int mul(Mod_Int a) { return a; }template <typename... args_t>constexpr friend Mod_Int mul(Mod_Int a, args_t... args) {return a * mul(args...);}template <typename... args_t>constexpr friend Mod_Int& toadd(Mod_Int& a, args_t... b) {return a = add(a, b...);}template <typename... args_t>constexpr friend Mod_Int& tomul(Mod_Int& a, args_t... b) {return a = mul(a, b...);}template <T __mod = mod, typename = std::enable_if_t<is_prime(__mod)>>static constexpr inline T inv(T a) {assert(a != 0);return _pow(a, mod - 2);}constexpr Mod_Int& operator + () const {return *this;}constexpr Mod_Int operator - () const {return _sub(0, val);}constexpr Mod_Int inv() const {return inv(val);}constexpr friend inline Mod_Int operator + (Mod_Int a, Mod_Int b) {return _add(a.val, b.val);}constexpr friend inline Mod_Int operator - (Mod_Int a, Mod_Int b) {return _sub(a.val, b.val);}constexpr friend inline Mod_Int operator * (Mod_Int a, Mod_Int b) {return _mul(a.val, b.val);}constexpr friend inline Mod_Int operator / (Mod_Int a, Mod_Int b) {return _mul(a.val, inv(b.val));}template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>constexpr friend inline Mod_Int operator ^ (Mod_Int a, _Tp p) {return _pow(a.val, p);}constexpr friend inline Mod_Int& operator += (Mod_Int& a, Mod_Int b) {return a = _add(a.val, b.val);}constexpr friend inline Mod_Int& operator -= (Mod_Int& a, Mod_Int b) {return a = _sub(a.val, b.val);}constexpr friend inline Mod_Int& operator *= (Mod_Int& a, Mod_Int b) {return a = _mul(a.val, b.val);}constexpr friend inline Mod_Int& operator /= (Mod_Int& a, Mod_Int b) {return a = _mul(a.val, inv(b.val));}template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>constexpr friend inline Mod_Int& operator ^= (Mod_Int& a, _Tp p) {return a = _pow(a.val, p);}constexpr friend inline bool operator == (Mod_Int a, Mod_Int b) {return a.val == b.val;}constexpr friend inline bool operator != (Mod_Int a, Mod_Int b) {return a.val != b.val;}constexpr Mod_Int& operator ++ () {this -> val + 1 == mod ? this -> val = 0 : ++this -> val;return *this;}constexpr Mod_Int& operator -- () {this -> val == 0 ? this -> val = mod - 1 : --this -> val;return *this;}constexpr Mod_Int operator ++ (int) {Mod_Int res = *this;this -> val + 1 == mod ? this -> val = 0 : ++this -> val;return res;}constexpr Mod_Int operator -- (int) {Mod_Int res = *this;this -> val == 0 ? this -> val = mod - 1 : --this -> val;return res;}friend std::istream& operator >> (std::istream& is, Mod_Int<mod, T, S>& x) {T ipt;return is >> ipt, x = ipt, is;}friend std::ostream& operator << (std::ostream& os, Mod_Int<mod, T, S> x) {return os << x.val;}protected:T val;static constexpr inline T _add(T a, T b) {return a >= mod - b ? a + b - mod : a + b;}static constexpr inline T _sub(T a, T b) {return a < b ? a - b + mod : a - b;}static constexpr inline T _mul(T a, T b) {return static_cast<S>(a) * b % mod;}template <typename _Tp, typename = std::enable_if_t<std::is_integral<_Tp>::value>>static constexpr inline T _pow(T a, _Tp p) {T res = 1;for (; p; p >>= 1, a = _mul(a, a))if (p & 1) res = _mul(res, a);return res;}};using mint = Mod_Int<>;using mod_t = mint;constexpr mint operator ""_m (unsigned long long x) {return mint(x);}constexpr mint operator ""_mod (unsigned long long x) {return mint(x);}
}using namespace Mod_Int_Class;
$\mathcal{O}(n^2)$ 部分分 & 打表
#include <cstdio>
#include <iostream>
#include <limits>
#include <cassert>
#include <vector>
using namespace std;int n, m;namespace $1 {bool check() {return n <= 1000;}void solve() {vector<vector<mint>> f(n + 1, vector<mint>(n + 1));for (int i = n - 1; i >= 0; --i) {vector<mint> k(i + 1), b(i + 1);k[0] = 1_mod * m / (n + m - i);b[0] = 1_mod * (n - i) / (n + m - i) * f[i + 1][0] + 1;for (int j = 1; j <= i; ++j) {k[j] = 1_mod * m / (n + m - i + j)+ 1_mod * j / (n + m - i + j) * k[j - 1];b[j] = 1_mod * (n - i) / (n + m - i + j) * f[i + 1][j]+ 1_mod * j / (n + m - i + j) * b[j - 1] + 1;}f[i][i] = b[i] / (1 - k[i]);for (int j = 0; j < i; ++j)f[i][j] = k[j] * f[i][i] + b[j];}printf("%d\n", f[0][0].raw());vector<vector<double>> g(n + 1, vector<double>(n + 1));for (int i = n - 1; i >= 0; --i) {vector<double> k(i + 1), b(i + 1);k[0] = 1. * m / (n + m - i);b[0] = 1. * (n - i) / (n + m - i) * g[i + 1][0] + 1;for (int j = 1; j <= i; ++j) {k[j] = 1. * m / (n + m - i + j)+ 1. * j / (n + m - i + j) * k[j - 1];b[j] = 1. * (n - i) / (n + m - i + j) * g[i + 1][j]+ 1. * j / (n + m - i + j) * b[j - 1] + 1;}g[i][i] = b[i] / (1 - k[i]);for (int j = 0; j < i; ++j)g[i][j] = k[j] * g[i][i] + b[j];for (int j = 0; j <= i; ++j)printf("%.10lf ", g[i][j]);puts("");// for (int j = 1; j <= i; ++j)//     printf("%.10lf ", g[i][j] - g[i][j - 1]);// puts("");}}
}signed main() {#ifndef XuYuemingfreopen("toad.in", "r", stdin);freopen("toad.out", "w", stdout);#endifscanf("%d%d", &n, &m);if ($1::check()) return $1::solve(), 0;$yzh::solve();return 0;
}
$\mathcal{O}(n \log M)$ 正解
namespace $yzh {const int N = 1000010;mint lambda[N], mu[N];void solve() {lambda[n] = mu[n] = 0;for (int i = n - 1; i >= 0; --i) {mu[i] = ((n - i) * mu[i + 1] - 1) / (n + m - i + 1);lambda[i] = i * mu[i] / (n - i) + lambda[i + 1] + mu[i + 1] + 1_mod * (n + m) / (n - i);}printf("%d", lambda[0].raw());}
}
卡常后
#pragma GCC optimize("Ofast", "inline", "fast-math", "unroll-loops")
#include <cstdio>const int N = 1000010, mod = 19260817;int n, m, lambda, mu, Inv[N << 1];
inline int add(int a, int b) { return a >= mod - b ? a + b - mod : a + b; }signed main() {freopen("toad.in", "r", stdin);freopen("toad.out", "w", stdout);scanf("%d%d", &n, &m), Inv[1] = 1;for (register int i = 2, *I = Inv + 2; i <= n + m + 1; ++i, ++I)*I = 1ll * (mod - Inv[mod % i]) * (mod / i) % mod;for (register int i = 1; i <= n; ++i) {int t = mu;mu = 1ll * add(1ll * i * mu % mod, mod - 1) * Inv[m + i + 1] % mod;lambda = add(1ll * add(n + m, 1ll * (n - i) * mu % mod) * Inv[i] % mod, add(lambda, t));}printf("%d", lambda);return 0;
}

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

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

相关文章

如何分发APP供用户使用?

​在移动互联网时代,APP(应用程序)已成为人们日常生活中不可或缺的一部分。无论是购物、社交、娱乐还是工作,APP都提供了极大的便利。然而,开发一款APP只是第一步,如何将其有效地分发到用户手中,让用户能够轻松下载并使用,才是决定APP成功与否的关键。 一、应用商店分发…

当然不是草台班子-冲刺计划

作业所属课程 软件工程2024作业要求 2024秋软工实践团队作业-第三次( Alpha冲刺)作业目标 做出alpha冲刺的计划团队名称 当然不是草台班子团队成员学号 姓名102201427 侯丽珂102201426 郑嘉祺102201241 戴康怡102201218 肖晗涵112200328 谢李东292300304 陈鹭102201242 魏儀阳…

设备管理系统功能拆解——设备档案管理

设备档案管理是设备管理系统中的一个核心组成部分,简单来说,它就是对设备的所有信息进行系统化的记录与管理,包括设备的基本数据、使用情况以及维护历史等。 如果一个工厂里数十台设备同时运转,没有准确的设备档案—— 设备出现故障时,管理者如何能迅速找到相关信息? 当需…

jenkins环境搭建

linux安装maven及环境配置 一、maven下载地址,安装binaries版本 https://dlcdn.apache.org/maven/maven-3/3.8.8/ 或者使用wget命令命令:wget https://https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-src.tar.gz二、解压命令:tar -zxvf apache-…

快速上手 KSQL:轻松与数据库交互的利器

上次我们通过 Docker 安装了 KingbaseES 数据库,今天我们将开始学习并快速上手使用 KSQL。简单来说,KSQL 本质上是一个客户端工具,用于与数据库进行交互。启动后,我们可以像使用普通的 SQL 一样执行查询和操作,这将是一个很好的练习机会。 同时,我们也可以借此机会探索 K…

数字能量的职场释放:实物量法诠释效率的多维度

在数字时代,职场对效率的要求不再单纯体现在“加快节奏”或“完成任务”上,更需要具备一种深度理解和动态管理的能力。我们常听到“全局观”“统筹管理”等术语,但如何实现这一点?如何在多维度的职场场景中释放效率,实现自我与团队的数字化能量? 这里引入一个概念——“实…

无聊的我啊~~~~~

本文来自博客园,作者:一石数字欠我15w!!!,转载请注明原文链接:https://www.cnblogs.com/52-qq/p/18545476

delphi cxgrid 增加选择框

cxgrid是自带选择框的表格,开启 设置如下。 然后 后台获取 前端选中的记录字段值。用下面语句。for I := 0 to Gview1.DataController.GetSelectedCount-1 do begin // 第一个值选中的 行 。 第二个值要获取值的字段索引 从 0 开始。。这里获取第2个字段 ShowMessag…

刷题答题脚本—接入AI (python+css)

刷题答题脚本———接入AI github地址:https://github.com/LY-zhang-yi-hao/do-homework-AI-python 创作不易,github点个star,有时间分享更多实用代码,谢谢! 本项目使得刷题的脚本的编写下限变低,利用阿里云模型回答问题,不需要进行题库的编写或网页搜索。 将问题发送给…

我的前端成长之路

原作者:玉伯 链接:https://www.yuque.com/yubo/morning/grow-up-at-alibaba 来源:语雀

【Unity】仓库逻辑:拾取物体进仓库和扔掉物品

unity中实现仓库逻辑:玩家前后左右移动,碰到的物体放入仓库,点击仓库中的物体扔回3D中需求说明 目标:实现玩家移动过程中,拾取物体,物体被放入仓库;点击仓库中物体,重新扔回3D场景中逻辑。 逻辑分析:需要玩家可以移动; 需要检测玩家和物体的碰撞,并摧毁物体; 需要识…

为什么卷积现在不火了:CNN研究热度降温的深层原因分析

在深度学习领域,卷积神经网络(CNN)曾经是计算机视觉的代名词。自2012年AlexNet在ImageNet竞赛中取得突破性成功以来,CNN在图像识别、目标检测等领域掀起了一场革命。然而,纵观近年的顶会论文和研究热点,我们不得不承认一个现实:CNN相关的研究论文正在减少,曾经的"…