概率期望 dp
概率期望 dp#
概念#
期望,是每次可能结果的概率乘上结果的权值的总和。
对于一个变量
举个例子:如果你有一个骰子,每次可以扔出
性质#
线性性#
若随机变量
- 对于任意实数
,都有 。 。
随机变量乘积的期望#
若随机变量
综上,当随机变量
概率期望 dp#
通常在求解达到某一目标的期望代价时,我们并不知道最终代价,因此,通常我们采用 倒序 的方式进行 dp。
Gym-105284C#
题意#
有一个由
思路#
我们先画出一条链:
我们考虑连通块的数量变化。
- 当图中的点的数量
时,连通块的数量也 。 - 当图中的边的数量
时,连通块的数量 。

我们再写出每个点和每条边保留的概率,对于点
代码#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e6 + 10, mod = 1e9 + 7;
int T, n;
ll inv[N], ans[N];
void Solve() {
cin >> n;
cout << ans[n] << '\n';
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
inv[1] = 1;
for (int i = 2; i < N; i++) {
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
for (int i = 1; i < N; i++) {
ans[i] = (ans[i - 1] + inv[i] - inv[i] * inv[i - 1] % mod + mod) % mod;
}
cin >> T;
while (T--) Solve();
return 0;
}
CF1778D#
题意#
给定两个长度为
求出第一次使得
思路 1#
设
其中,选中不同的位置的概率是
而选中原本已经相同的位置的概率是
初始状态
但是这种转移是不存在拓扑序的,状态
我们设
又有
设
代码 1#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll, ll>;
const int N = 1e6 + 10, mod = 998244353;
int T, n;
ll inv[N];
string a, b;
pii dp[N];
ll qpow(ll x, ll y) {
if (!y) return 1;
ll tmp = qpow(x, y / 2);
return tmp * tmp % mod * (y & 1 ? x : 1) % mod;
}
void Solve() {
cin >> n >> a >> b;
int cnt = 0;
for (int i = 0; i < n; i++) cnt += a[i] != b[i];
dp[0] = {0, 0}, dp[1] = {1, 0};
for (int i = 2; i <= n; i++) {
ll p = (i - 1) * inv[n] % mod, q = n * inv[n - i + 1] % mod;
dp[i].first = (dp[i - 1].first - p * dp[i - 2].first % mod + mod) % mod * q % mod;
dp[i].second = (dp[i - 1].second - 1 - dp[i - 2].second * p % mod + mod) % mod * q % mod;
}
ll p = (1 + dp[n - 1].second % mod - dp[n].second + mod) % mod;
ll q = (dp[n].first - n * inv[n] % mod * dp[n - 1].first % mod + mod) % mod;
ll k = p * qpow(q, mod - 2) % mod;
cout << (k * dp[cnt].first % mod + dp[cnt].second) % mod << '\n';
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
inv[1] = 1;
for (int i = 2; i < N; i++) {
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
cin >> T;
while (T--) Solve();
return 0;
}
思路 2#
设
这种时候转移有两种情况:
- 修改的位置恰好不同,概率是
。 - 修改的位置是相同的,那么不同的位置的数量在操作后就会变成
个,概率是 ,那么之后还需要从 转移到 ,再转移到 。
因此,我们的转移是:
移项后就会变成:
代码 2#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll, ll>;
const int N = 1e6 + 10, mod = 998244353;
int T, n;
ll inv[N], dp[N];
string a, b;
void Solve() {
cin >> n >> a >> b;
int cnt = 0;
for (int i = 0; i < n; i++) cnt += a[i] != b[i];
dp[n] = 1;
ll ans = (n == cnt);
for (int i = n - 1; i >= 1; i--) {
dp[i] = (n * inv[i] % mod + (n * inv[i] % mod + mod - 1) % mod * dp[i + 1] % mod) % mod;
if (i <= cnt) (ans += dp[i]) %= mod;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
inv[1] = 1;
for (int i = 2; i < N; i++) {
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}
cin >> T;
while (T--) Solve();
return 0;
}
洛谷 P8774#
题意#
有一只甲壳虫想要爬到高度
思路#
我们考虑上个例题的思路 1,还是令
那么转移就是:
初始状态为
然后就像上一题一样解方程即可。
代码#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll, ll>;
const int N = 1e6 + 10, mod = 998244353;
int n;
ll p[N];
pii dp[N];
ll qpow(ll x, ll y) {
if (!y) return 1;
ll tmp = qpow(x, y / 2);
return tmp * tmp % mod * (y & 1 ? x : 1) % mod;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1, x, y; i <= n; i++) cin >> x >> y, p[i] = x * qpow(y, mod - 2) % mod;
dp[n] = {0, 0}, dp[n - 1] = {p[n], 1};
for (int i = n - 2; i >= 0; i--) {
dp[i].first = (p[i + 1] + dp[i + 1].first - p[i + 1] * dp[i + 1].first % mod + mod) % mod;
dp[i].second = ((mod + 1 - p[i + 1]) % mod * dp[i + 1].second % mod + 1) % mod;
}
ll p = (mod + 1 - dp[0].first) % mod;
ll k = dp[0].second * qpow(p, mod - 2) % mod;
cout << k;
return 0;
}
CF280C#
题意#
给定一颗有
每次操作都会从剩下的结点中平均的选择一个结点,请求出操作数量的期望。
思路#
我们考虑每个结点
对于一个结点
很显然的,结点
我们又可以发现,这个概率只与
所以,我们考虑在
直接算出深度求和即可。
代码#
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n;
vector<int> g[N];
double ans;
void dfs(int u, int fa, int dep) {
ans += 1.0 / dep;
for (int v : g[u]) {
if (v != fa) dfs(v, u, dep + 1);
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
g[u].push_back(v), g[v].push_back(u);
}
dfs(1, 0, 1);
cout << fixed << setprecision(7) << ans;
return 0;
}
abc333 F#
题意#
有
我们需要重复一个操作,直到队伍中只剩下一个人,每次操作有
对于每一个
思路#
我们设
很显然,初始状态为
对于其他的状态,则有
其中,我们有
同样的,还有另外
但是,我们发现转移中出现了环,因此,我们考虑像上两题一样解方程的做法。
代码#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pii = pair<ll, ll>;
const int N = 3010, mod = 998244353;
int n;
pii dp[N][N];
ll qpow(ll x, ll y) {
if (!y) return 1;
ll tmp = qpow(x, y / 2);
return tmp * tmp % mod * (y & 1 ? x : 1) % mod;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
ll inv = (mod + 1) / 2;
dp[1][1] = {0, 1};
for (int i = 2; i <= n; i++) {
dp[i][1] = {1, 0};
for (int j = 2; j <= i; j++) {
dp[i][j].first = dp[i][j - 1].first * inv % mod;
dp[i][j].second = (dp[i - 1][j - 1].second + dp[i][j - 1].second) % mod * inv % mod;
}
ll p = dp[i][i].second, q = (2 - dp[i][i].first + mod) % mod;
ll k = p * qpow(q, mod - 2) % mod;
dp[i][1].second = k;
for (int j = 2; j <= i; j++) {
dp[i][j].second = (dp[i - 1][j - 1].second + dp[i][j - 1].second) % mod * inv % mod;
}
}
for (int i = 1; i <= n; i++) cout << dp[n][i].second << ' ';
return 0;
}
CF1418E#
题意#
有
当你对抗力量为
-
如果
,你会收到 点伤害。 -
如果
,你不会受到伤害,但是盾牌的耐久度会减少 。 -
如果
,则什么都不会发生。
你将会按照某种随机的顺序对抗怪物,对于每一个
思路#
首先,我们会发现,对于一个盾牌
假设对于盾牌
首先,如果
那么,对于
分为两种情况:
- 如果
,这样的概率是 。 - 如果
,概率是 。
可以发现,对于这两种怪物,每种怪物中的每一个被选中的概率都是一样的,所以可以直接合并到一起,用前缀和处理。
代码#
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e5 + 10, mod = 998244353;
int n, m, d[N];
ll sum[N], inv[N];
ll qpow(ll x, ll y) {
if (!y) return 1;
ll tmp = qpow(x, y / 2);
return tmp * tmp % mod * (y & 1 ? x : 1) % mod;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m, inv[1] = 1;
for (int i = 2; i <= n; i++) inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for (int i = 1; i <= n; i++) cin >> d[i];
sort(d + 1, d + n + 1);
for (int i = 1; i <= n; i++) sum[i] = (sum[i - 1] + d[i]) % mod;
while (m--) {
int a, b; cin >> a >> b;
int pos = lower_bound(d + 1, d + n + 1, b) - d - 1;
int x = n - pos;
ll ans = 0;
if (x >= a) {
ans = ((x - a) * inv[x] % mod * (sum[n] - sum[pos] + mod) % mod + (x - a + 1) * inv[x + 1] % mod * sum[pos] % mod) % mod;
}
cout << ans << '\n';
}
return 0;
}
CF1874C#
题意#
有
有两个人一起从
如果两个人选择的边的终点是相同的,他们就会沿着这条边走到下一个城市;否则,这两条道路都将被摧毁。
如果从
现在,其中的一个人知道另一人会随机选择一条边,但是他会最优选择道路。
求出任务成功的最大概率。
思路#
设
很显然,对于
所以我们按照这个顺序考虑
设
很显然的,
假设当前有
- 被选中的另一条边在
之前,则选中一条既不是第 条,也不是第 条的边概率就是 ,在这种情况下,边的总数就会减少 ,第 条边的编号也会往前挪两位。 - 被选中的另一条边在
之后,则选中这样的边的概率就是 ,在这种情况下,边的总数还是会减少 ,但是第 条边的编号只会往前挪 位。
所以,转移就是:
我们再重新考虑
注意要提前预处理出所有的
代码#
#include <bits/stdc++.h>
using namespace std;
using db = double;
const int N = 5010;
int T, n, m;
db p[N][N], dp[N];
vector<int> g[N];
void Solve() {
cin >> n >> m;
while (m--) {
int u, v; cin >> u >> v;
g[u].push_back(v);
}
dp[n] = 1;
for (int i = n - 1; i >= 1; i--) {
sort(g[i].begin(), g[i].end(), [](int i, int j) {return dp[i] > dp[j];});
int k = g[i].size();
for (int j = 0; j < k; j++) {
dp[i] += dp[g[i][j]] * p[k][j + 1];
}
}
cout << fixed << setprecision(10) << dp[1] << '\n';
fill(dp + 1, dp + n + 1, 0);
for (int i = 1; i <= n; i++) g[i].clear();
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
for (int j = 1; j < N; j++) {
p[j][1] = 1.0 / j;
for (int t = 2; t <= j; t++) {
p[j][t] = (t - 2) * 1.0 / j * p[j - 2][t - 2] + (j - t) * 1.0 / j * p[j - 2][t - 1];
}
}
cin >> T;
while (T--) Solve();
return 0;
}
CF1866M#
题意#
作者:cn
出处:https://www.cnblogs.com/chengning0909/p/18344962
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下