AT_abc323_e Playlist 题解
AT_abc323_e Playlist 题解
一道概率论和动态规划的题,这里的题解主要是翻译原文和个人理解。
问题陈述
有一个包含 \(n\) 首歌曲的播放列表。歌曲 \(i\space(1 \leq i \le n)\) 的长度为整数 \(t_i\) 秒。从第 \(0\) 秒开始随机播放该播放列表。
随机播放重复以下步骤:从 \(n\) 首歌曲中以相等的概率选择一首歌曲,将该歌曲播放完。一首歌播放完,另一首歌就会立即开始播放。同一首歌可以连续选择。
求歌曲 \(1\) 在 \((x + 0.5)\) 秒正在播放的概率(即第 \(1\) 首歌正在被播放),答案对 \(998244353\) 取模。
如何取模
可以证明,本题中要求得的概率总是一个有理数。另外,这个问题的约束条件保证了当要求得的概率用既约分数 \(\frac{y}{x}\) 表示时,\(x\) 不能被 \(998244353\) 整除。
那么,在 \(0\) 和 \(998244352\)(含)之间有一个唯一的整数 \(z\),使得 \(xz \equiv y \pmod{998244353}\),请输出 \(z\)。
题目分析
设 \(f(i)\) 为在第 \(i\) 秒切换歌曲的概率,\(i\in[0,x]\),对 \(998244353\) 取模。
事件「歌曲 \(1\) 在 \(x+0.5\) 秒播放」,与事件「该歌曲在 \(x,(x-1),\ldots,(x-t_1+1)\) 秒开始播放」是互斥的,即这些事件只可能有一个发生。
因此,只需求得歌曲 \(1\) 在时间 \(i\in\{x,(x-1),\ldots,(x-t_1+1)\}\) 的概率之和即可。
因为歌曲的长度一定为整数,因此如果歌曲 \(1\) 在其中某一个时间 \(i\) 播放,那么这首歌曲一定在 \(x+0.5\) 秒正在播放。
因为选择歌曲的概率相等,因此歌曲 \(1\) 在时间 \(i\) 开始播放的概率为任意歌曲在时间 \(i\) 开始播放的概率的 \(\frac{1}{n}\)。
因此,\(\text{ans}=\frac{1}{n}\sum_{j=\max\{x-t_1+1,0\}}^{x}f_j\)。
因此,只需找到每个 \(f(i)\) 即可,可以通过动态规划计算。
显然的,\(f(0)=1\),\(f(i)=\frac{1}{n}\sum_{j=1}^{n}f(i-t_j)\)。
可以这么理解,对于 \(i\ge1\),我们考虑哪首歌曲在 \(i\) 时刻结束,这样 \(p(i)\) 就可以表示为:歌曲 \(j\in[1,n]\) 在 \((i-t_j)\) 时刻开始的概率。正如我们已经讨论过的,\(i-t_j<0\Rightarrow0\),\(i-t_j\ge0\Rightarrow\frac{1}{n}\times f(i-t_j)\)。因此计算 \(f(i)=\frac{1}{n}(f[i-t_1]+f[i-t_2]+\cdots+f[i-t_n])\) 就足够了。
计算每个 \(f(i)\) 需要花费 \(\mathcal{O}(n)\) 时间,计算所有 \(i\in[1,x]\) 的总时间复杂度是 \(\mathcal{O}(nx)\);然后,计算答案需要花费 \(\mathcal{O}(\min\{t_1,x\})\)时间。因此,总体复杂度为 \(\mathcal{O}(nx)\),足以快速解决问题。
注意除法取模需要用到模意义下的乘法逆元,详见我的文章:https://www.cnblogs.com/RainPPR/p/linear-congruence-equation-and-inverse.html。
示例代码
#include <bits/stdc++.h> using namespace std; using ll = long long; const ll MOD = 998244353; #define rr(x) read<x>() template<typename _tp> inline _tp read() { _tp num = 0, flag = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1; for (; isdigit(ch); ch = getchar()) num = num * 10 + ch - '0'; return num * flag; } ll qpow(ll a, ll b, ll mod) { ll res = 1; for (; b; b >>= 1, a = a * a % mod) if (b & 1) res = res * a % mod; return res; } int t[1010]; ll f[20010]; int main() { int n = rr(int), x = rr(int); int vn = qpow(n, MOD - 2, MOD); for (int i = 1; i <= n; ++i) t[i] = rr(int); f[0] = 1; for (int i = 1; i <= x; ++i) { for (int j = 1; j <= n; ++j) if (i >= t[j]) f[i] = (f[i] + f[i - t[j]]) % MOD; f[i] = f[i] * vn % MOD; } ll res = 0; for (int i = max(0, x - t[1] + 1); i <= x; ++i) { res = (res + f[i]) % MOD; } res = res * vn % MOD; printf("%lld\n", res); return 0; }
本文来自博客园,作者:RainPPR,转载请注明原文链接:https://www.cnblogs.com/RainPPR/p/solution-AT_abc323_e.html
如有侵权请联系我(或 2125773894@qq.com)删除。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!