期望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\) 位的期望都是这个数。
严谨证明如下:
对于第 \(2\) 到 \(8\) 位,第 \(1\) 位的所有取值对其存在不同的影响。
分类讨论一下 \(7\) 种情况,就会发现末项和为 \(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\) 的限制,因此其转移方程变为
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] 游戏
- 用期望的方法。
考虑求出所有方案中答案的期望再乘上 \(n!\)。
我们称关键数为在 \([l,r]\) 中不存在除自己外的因数的数,
每一种方案的答案即为最后一个关键数的位置。
考虑这个相对有些困难,我们不妨将其转换为最后一个关键数后面的非关键数的个数的期望。
考虑先剔除所有非关键数,设存在 \(k\) 个非关键数。
那么任意一个非关键数放到最后一个关键数后面的期望即为
处于最后一个关键数后面的非关键数的个数期望即为
答案的期望即为
乘上方案数即为
- 不用期望的方法。
同上,我们知道了每一种方案的答案即为最后一个关键数的位置。
那么考虑枚举最后一个关键数的位置。
当最后一个关键数的位置为 \(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;
}
浙公网安备 33010602011771号