1 条题解
-
0
收音机
本题要求计算在随机播放模式下,在时刻 秒时,第1首曲子正在播放的概率。注意,由于曲目连续播放且每首曲子都是完整播放,播放时刻总是累加各曲目时长。设当前累积播放时间为 ,若下一首曲子为第1首且其时长为 ,要使得第1首曲子正在播放,则需要满足
由于 为整数,而 总落在两个整数之间,可以转换为
也就是 。因此,题目的求解思路转化为:
- 计算所有可能的播放序列,其总播放时长恰好为 秒的概率,其中 从 到 之间;
- 对于满足 的状态,若下一首歌选中第1首的概率为 (因为随机选取),则累加这些状态的概率乘以 。
我们注意到,给定每首歌的选择概率都是 ,因此在进行状态转移时,每加入一首歌曲都乘以 。另外,为了求得最终的概率模 ,需要利用快速幂求模逆元的技巧,即求出 。总体来说,本题通过状态动态规划(DP)的方式累积所有可能的播放序列,并利用概率转移来计算精确的概率,最后再乘以一个额外的 来确保下一首歌为第1首。
该方法的核心在于将连续随机过程转化为求和问题,每个状态的概率由前面状态转移而来。数学公式部分,我们需要求出下列概率的和:
其中 表示累积时间恰好为 的概率,而 通过状态转移满足
$$f(S)=\sum_{j=1}^{N} \frac{1}{N} \cdot f(S-T_j) \quad \text{(当 } S\ge T_j\text{)} $$由于 ,即初始状态的概率为 1。
算法采用动态规划(DP)思想。我们定义一维数组 表示经过一系列曲目之后,总播放时长恰好为 秒的概率。初始状态为 。由于每次播放选择曲目概率均为 ,状态转移方程可以写为
$$f[i] = \sum_{j=1}^{N} \frac{1}{N}\cdot f[i-T_j] \quad \text{对于 } i \ge T_j $$在实际实现时,我们将 转换为模 意义下的乘法逆元,记为 。因此,状态转移公式便变为
$$f[i] = \sum_{j=1}^{N} \left( f[i-T_j] \cdot ni \right) \pmod{998244353} $$我们遍历 从 到 ,对每个 ,遍历所有曲目 (),如果 ,则更新状态 。在所有状态转移完成后,我们需要统计所有满足
的 。这里的区间表示:若前面的曲目累计时间 在这个区间内,则下一首曲目(若为第1首)能够覆盖时刻 。最后答案为上面和再乘以一次 (即 ),得到最终概率值。
整个过程在模 下计算,因此每一步计算都做模运算,避免大数问题。同时,为了计算模逆元,我们使用快速幂算法,该算法的数学公式为
这里 是素数。这样能确保逆元计算在对数时间内完成,从而保证整体算法的时间复杂度在允许范围内。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const ll MOD = 998244353; ll f[20000], t[20000]; ll qp(ll x, ll y) { ll ret = 1; while (y) { if (y & 1) { ret = (ret * x) % MOD; } x = (x * x) % MOD; y >>= 1; } return ret; } int main() { ll n, x; cin >> n >> x; for (int i = 1; i <= n; i++) { cin >> t[i]; } f[0] = 1; ll ni = qp(n, MOD - 2); for (int i = 0; i <= x; i++) { for (int j = 1; j <= n; j++) { if (i >= t[j]) { f[i] = (f[i] + f[i - t[j]] * ni) % MOD; } } } ll ans = 0; for (int i = max(0LL, x - t[1] + 1); i <= x; i++) { ans = (ans + f[i]) % MOD; } cout << (ans * ni) % MOD; return 0; }
import java.io.*; import java.util.StringTokenizer; public class Main { static final long MOD = 998244353; // 快速幂求逆元函数 static long qp(long x, long y) { long ret = 1; while (y > 0) { if ((y & 1) == 1) { ret = ret * x % MOD; } x = x * x % MOD; y >>= 1; } return ret; } public static void main(String[] args) throws IOException { // 输入输出加速,统一按 token 读取 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out))); StringTokenizer st = new StringTokenizer(br.readLine()); // 读取所有输入token int n = Integer.parseInt(st.nextToken()); int X; if (st.hasMoreTokens()) { X = Integer.parseInt(st.nextToken()); } else { X = Integer.parseInt(br.readLine().trim()); } long[] t = new long[n + 1]; // 若后续token不足则继续读取下一行 st = new StringTokenizer(br.readLine()); for (int i = 1; i <= n; i++) { if (!st.hasMoreTokens()) { st = new StringTokenizer(br.readLine()); } t[i] = Long.parseLong(st.nextToken()); } // 定义dp数组,时间范围为 0~X long[] f = new long[X + 1]; f[0] = 1; long ni = qp(n, MOD - 2); // 计算1/n的逆元 // 动态规划状态转移 for (int i = 0; i <= X; i++) { for (int j = 1; j <= n; j++) { if (i >= t[j]) { f[i] = (f[i] + f[i - (int)t[j]] * ni) % MOD; } } } // 统计所有满足 [max(0, X-t[1]+1), X] 的状态 long ans = 0; int start = Math.max(0, X - (int)t[1] + 1); for (int i = start; i <= X; i++) { ans = (ans + f[i]) % MOD; } // 乘上选中第一首曲子的概率 out.println(ans * ni % MOD); out.flush(); out.close(); br.close(); } }
import sys input = sys.stdin.readline MOD = 998244353 def qp(x, y): ret = 1 while y: if y & 1: ret = ret * x % MOD x = x * x % MOD y //= 2 return ret def main(): data = sys.stdin.read().split() n = int(data[0]) X = int(data[1]) t = [0] * (n + 1) for i in range(1, n+1): t[i] = int(data[i+1]) f = [0] * (X + 1) f[0] = 1 ni = qp(n, MOD - 2) # 1/n 的逆元 for i in range(0, X + 1): for j in range(1, n + 1): if i >= t[j]: f[i] = (f[i] + f[i - t[j]] * ni) % MOD ans = 0 start = max(0, X - t[1] + 1) for i in range(start, X + 1): ans = (ans + f[i]) % MOD sys.stdout.write(str(ans * ni % MOD)) if __name__ == "__main__": main()
信息
- ID
- 298
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 9
- 标签
- 递交数
- 10
- 已通过
- 6
- 上传者