• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
The blog of EzSun
lyy && ?
博客园    首页    新随笔    联系   管理    订阅  订阅

期望dp总结

P4316 绿豆蛙的归宿

很经典一道题。
这里着重讲一下为什么顺推要处理概率,不可以直接转移和逆推为什么没有问题。
因为顺推转移方程为 \(f_i=\sum_{j \rightarrow i}^{} p_{j \rightarrow i} \times (f_j+w_{j,i})\)。
其中 \(p_{j \rightarrow i}\) 为在所有到达 \(i\) 的方法中,这种方法的概率即所占比例。
但这个东西很明显是很难处理的。
所以我们可以换一种方法。
设 \(p_i\) 为由 \(1\) 走到 \(i\) 的距离。
转移方程即为 \(f_i=\sum_{j \rightarrow i}^{} \frac{f_j+w_{j,i} \times p_j}{out_j}\)。
可以理解为 \(f_j\) 部分已经计算了走到 \(j\) 的概率,因此只需给 \(w_{j,i}\) 乘上 \(p_j\) 的贡献。
但由于从一个点走向所有出边的概率是相等的,因此逆推的转移就没有问题。

Code:

顺推:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 7;
int n, m;
double p[N], f[N];
struct Edge{
	struct edge{
		int to;
		ll w;
		int pre;
	}e[N << 1];
	int head[N], tot;
	int in[N], out[N];
	int q[N], hh = 1, tt;
	void add(int x, int y, ll z){
		e[++tot] = {y, z, head[x]};
		head[x] = tot;
		out[x]++;
		in[y]++;
	}
	void Topo(){
		q[++tt] = 1;
		p[1] = 1;
		while(hh <= tt){
			int u = q[hh++];
			for(int i = head[u]; i; i = e[i].pre){
				int v = e[i].to;
				f[v] += (f[u] + (e[i].w * p[u])) / out[u];
				p[v] += p[u] / out[u];
				in[v]--;
				if(!in[v]) q[++tt] = v;
			}
		}
	}
}E;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int x, y;
		ll z;
		cin >> x >> y >> z;
		E.add(x, y, z);
	}
	E.Topo();
	printf("%.2lf", f[n]);
	return 0;
}

逆推:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 7;
int n, m;
double f[N];
struct Edge{
	struct edge{
		int to;
		ll w;
		int pre;
	}e[N << 1];
	int head[N], tot;
	int in[N], out[N];
	int q[N], hh = 1, tt;
	void add(int x, int y, ll z){
		e[++tot] = {y, z, head[x]};
		head[x] = tot;
		out[y]++;
	}
	void Topo(){
		q[++tt] = n;
		memcpy(in,out,sizeof in);
		while(hh <= tt){
			int u = q[hh++];
			for(int i = head[u]; i; i = e[i].pre){
				int v = e[i].to;
				f[v] += (f[u] + e[i].w) * 1.0 / out[v];
				in[v]--;
				if(!in[v]) q[++tt] = v;
			}
		}
	}
}E;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int x, y;
		ll z;
		cin >> x >> y >> z;
		E.add(y, x, z);
	}
	E.Topo();
	/*for(int i = 1; i <= n; i++){
		printf("%d:%.1lf\n", i, f[i]);
	}*/
	printf("%.2lf", f[1]);
	return 0;
}

P3802 小魔女帕琪

很好一道题目。
先分析前 \(7\) 位的期望,
不难发现位置不影响答案,
因此期望为

\[7! \times \prod_{i=1}^{i \leq 7} \frac{a_i}{n-i+1} \]

但感性理解一下,可以发现后面每 \(7\) 位的期望都是这个数。
严谨证明如下:
对于第 \(2\) 到 \(8\) 位,第 \(1\) 位的所有取值对其存在不同的影响。
分类讨论一下 \(7\) 种情况,就会发现末项和为 \(1\)。
那么答案为

\[7! \times (n-6) \times \prod_{i=1}^{i \leq 7} \frac{a_i}{n-i+1} \]

Code:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int n = 7;
ll s;
double ans = 1;
int a[10];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	for(int i = 1; i <= n; i++) cin >> a[i], s += a[i];
	for(int i = 1; i < n; i++){
		ans *= 1.0 * a[i] / (s - i + 1) * i;
	}
	ans *= a[7] * 7;
	printf("%.3lf\n", ans);
	return 0;
}

P6046 纯粹容器

不难的一道题。
设其左边第一个比 \(i\) 大的数为 \(pre_i\),右边第一个比其大的数为 \(nxt_i\)。
不难发现每个物品的期望 \(E=\sum_{i=1}^{i \leq n-1} P(至少活i轮)\),其中 \(P(i)\) 为该物品在第 \(i\) 轮没死的概率。
没死的概率比较难求,考虑转化为 \(1-P(在第i轮死去)\)。
我们将整个序列看作一条链,将一次操作看作消掉一条边,那么物品 \(i\) 死去则当且仅当 \(pre_i\) 或 \(nxt_i\) 同 \(i\) 之间的边都被消除。
设 \(dl=i-pre_i\),\(dr=nxt_i-i\),则 \(P(至少活i轮)=1-(在第i轮死去)=1-(P(左边消除)+P(右边消除)-P(左右消除))=1-(\frac{i-dl}{n-1-dl}+ \frac{i-dr}{n-1-dr}- \frac{i-dl-dr}{n-1-dl-dr})\)。

Code:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 57, mod = 998244353;
int n;
int a[N];
int lenl[N], lenr[N];
int C[N][N];
ll fpow(ll x, ll y = mod - 2){
	ll sum = 1;
	while(y){
		if(y & 1) sum = sum * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return sum;
}
void init(){
	C[0][0] = 1;
	for(int i = 1; i <= N - 7; i++){
		C[i][0] = 1;
		for(int j = 1; j <= N - 7; j++){
			C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
		}
	}
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	init();
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= n; i++){
		for(int j = i - 1; j; j--){
			if(a[j] > a[i]){
				lenl[i] = i - j;
				break;
			}
		}
		for(int j = i + 1; j <= n; j++){
			if(a[j] > a[i]){
				lenr[i] = j - i;
				break;
			}
		}
	}
	for(int i = 1; i <= n; i++){
		int dl = lenl[i], dr = lenr[i];
		ll sum = 0;
		for(int j = 1; j < n; j++){
			ll A = 0, B = 0, AB = 0;
			if(dl && j >= dl){
				A = C[n - 1 - dl][j - dl] * fpow(C[n - 1][j]) % mod;
			}
			if(dr && j >= dr){
				B = C[n - 1 - dr][j - dr] * fpow(C[n - 1][j]) % mod;
			}
			if(dl && dr && j >= dl + dr){
				AB = C[n - 1 - dl - dr][j - dl - dr] * fpow(C[n - 1][j]) % mod;
			}
			sum = (sum + (1 - A - B + AB) % mod + mod) % mod;
		}
		printf("%lld ", sum);
	}
	return 0;
}

P4550 收集邮票

很经典的一道题目。
考虑设 \(f_i,g_i\) 分别为次数期望和代价期望。
当有 \(i-1\) 张邮票的时候,获得新邮票的概率即为 \(\frac{n-i+1}{n}\),期望次数即为其倒数即 \(\frac{n}{n-i+1}\)。
因此 \(f\) 的转移方程为 \(f_i=f_{i-1}+ \frac{n}{n-i+1}\)。
不难发现 \(f_i\) 刚好是单次购买时的单价。
再乘上期望的购买次数便可以得到方程 \(g_i=g_{i-1}+f_i \times \frac{n}{n-i+1}\)。

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 10007;
int n;
double f[N], g[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++){
		f[i] = f[i - 1] + double(1.0 * n / (n - i + 1));
		g[i] = g[i - 1] + f[i] * double(1.0 * n / (n - i + 1));
	}
	printf("%.2lf\n", g[n]);
	return 0;
}

P1654 OSU!

一道很典型的高次期望题目。
不难发现可以设 \(l_i,f_i,g_i\) 第 \(i\) 位以 \(1\) 结尾的一、二、三次期望。
考虑一下如何转移。
\(l_i\) 的转移是比较简单的,\(l_i=p_i \times (l_{i-1}+1)\)。
接下来考虑高次的转移。
首先要明确一个点:多次的期望不等于期望的多次,即 \(E(x^k) \neq E(x)^k\)。
这也意味着同次的转移不可以由低次合成,这也是这一题不可以只处理期望的一次方是原因。
那么根据二项式定理,\(f_i=p_i \times (f_{i-1}+2 \times l_{i-1}+1)\)。
同理,\(g_i=p_i \times (g_{i-1} + 3 \ times f_{i-1} + 3 \ times l_{i-1} + 1)\)。
那么最终答案似乎就是 \(g_n\) 吗?
回到状态的定义上,我们发现状态强制末位为 \(1\)。
我们便可以对于 \(g\) 取消末位为 \(1\) 的限制,因此其转移方程变为

\[g_i=p_i \times (g_{i-1} + 3 \times f_{i-1} + 3 \times l_{i-1} + 1) + (1-p_i) \times g_{i-1} = g_{i-1} + p_i \times (3 \times f_{i-1} + 3 \times l_{i-1} + 1) \]

Code:

#include<bits/stdc++.h>
using namespace std;
const int N = 100007;
int n;
double p[N];
double len, f[N], g[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> p[i];
	for(int i = 1; i <= n; i++){
		g[i] = g[i - 1] + p[i] * (3 * (f[i - 1] + len) + 1);
		f[i] = p[i] * (f[i - 1] + 2 * len + 1); 
		len = p[i] * (len + 1);
	}
	printf("%.1lf\n", g[n]);
	return 0;
}

P4562 [JXOI2018] 游戏

  1. 用期望的方法。
    考虑求出所有方案中答案的期望再乘上 \(n!\)。
    我们称关键数为在 \([l,r]\) 中不存在除自己外的因数的数,
    每一种方案的答案即为最后一个关键数的位置。
    考虑这个相对有些困难,我们不妨将其转换为最后一个关键数后面的非关键数的个数的期望。
    考虑先剔除所有非关键数,设存在 \(k\) 个非关键数。
    那么任意一个非关键数放到最后一个关键数后面的期望即为

\[\frac{1}{k+1} \]

处于最后一个关键数后面的非关键数的个数期望即为

\[\frac{n-k}{k+1} \]

答案的期望即为

\[n - \frac{n-k}{k+1} = \frac{k(n+1)}{k+1} \]

乘上方案数即为

\[\frac{k}{k+1} \times (n+1)! \]

  1. 不用期望的方法。
    同上,我们知道了每一种方案的答案即为最后一个关键数的位置。
    那么考虑枚举最后一个关键数的位置。
    当最后一个关键数的位置为 \(i\) 时,方案数即为

\[\binom{n-k}{i-k} \times (i-1)! \times k \times (n-i)! \]

因此答案为

\[\sum_{i=k}^{i \leq n} i \times \binom{n-k}{i-k} \times (i-1)! \times k \times (n-i)! \]

Code(只有2喵):

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e7 + 7, mod = 1e9 + 7;
int L, R, n;
int fac[N], inv[N];
bool is[N];
ll fpow(ll x, ll y = mod - 2){
	ll sum = 1;
	while(y){
		if(y & 1) sum = sum * x % mod;
		x = 1ll * x * x % mod;
		y >>= 1;
	}
	return sum;
}
void init(){
	fac[0] = 1;
	for(int i = 1; i <= N - 7; i++) fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[N - 7] = fpow(fac[N - 7]);
	for(int i = N - 8; i >= 0; i--){
		inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	} 
}
ll C(int x, int y){
	return 1ll * fac[x] * inv[y] % mod * inv[x - y] % mod;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	init();              
	cin >> L >> R;
	n = R - L + 1;
	for(int i = L; 2 * i <= R; i++){
		if(is[i]) continue; 
		for(int j = 2 * i; j <= R; j += i){
			is[j] = 1;
		}
	}
	ll ans = 0;
	int k = 0;
	for(int i = L; i <= R; i++){
		if(!is[i]) k++;
	}
	for(int i = k; i <= n; i++){
		ans = (ans + 1ll * fac[n - k] * fac[i - 1] % mod * k % mod * inv[i - k] % mod * i % mod) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2026-05-09 15:52  EzSun599  阅读(5)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3