概率期望小结
UVA11762 Race to 1
f[x] 表示将 x 变为 1 的期望步数, p[x] 表示 <=x 的质数的个数, g[x] 表示 x 的质因数的个数
点击查看代码
#include <stdio.h>
typedef long long LL;
const int N = 1e6 + 5;
int T, cs, n = 1e6, primes[N], cnt;
bool st[N], vis[N]; double f[N];
void pre() {
for(int i = 2; i <= n; i ++) {
if(!st[i]) primes[cnt ++] = i;
for(int j = 0; (LL)i * primes[j] <= n; j ++) {
st[i * primes[j]] = true;
if(i % primes[j] == 0) break;
}
}
}
double dp(int x) { // 记忆化搜索
if(x == 1) return 0; // 边界
if(vis[x]) return f[x];
vis[x] = true;
int p = 0, g = 0; // p: p[x], g: g[x]
for(; p < cnt && primes[p] <= x; p ++)
if(x % primes[p] == 0) g ++, f[x] += dp(x / primes[p]);
return f[x] = (f[x] + p) / g;
}
int main() {
pre(), scanf("%d", &T);
while(T --) scanf("%d", &n), printf("Case %d: %.10lf\n", ++ cs, dp(n));
return 0;
}
UVA10288 优惠券 Coupons
点击查看代码
#include <stdio.h>
typedef __int128_t LLL;
char dig[100];
void write(LLL x) {
int cnt = 0; LLL t;
do t = x / 10, dig[++ cnt] = x - (t << 1) - (t << 3), x = t; while(x);
for(t = cnt; t; t --) putchar(dig[t] ^ 48);
}
LLL gcd(LLL x, LLL y) {
while(x) {
y %= x;
x ^= y ^= x ^= y;
}
return y;
}
int main() {
int n;
while(scanf("%d", &n) != EOF) {
LLL a = 0, b = 1;
for(int i = 1; i <= n; i ++) {
LLL c = 1;
for(int j = 1; j <= n; j ++)
if(j != i) c *= j;
a += c;
if(i < n) b *= i;
}
if(a % b == 0) write(a / b), putchar(10);
else {
LLL c = a / b, d = a % b, dd = gcd(d, b);
LLL cc = c; int len = 0, len2 = 0;
while(cc) cc /= 10, len ++;
cc = b / dd;
while(cc) cc /= 10, len2 ++;
for(int i = 0; i <= len; i ++) putchar(' ');
write(d / dd), putchar(10), write(c), putchar(' ');
for(int i = 0; i < len2; i ++) putchar('-');
putchar(10);
for(int i = 0; i <= len; i ++) putchar(' ');
write(b / dd), putchar(10);
}
}
return 0;
}
P1654 OSU!
点击查看代码
#include <stdio.h>
const int N = 100005;
int n;
double w;
double f[N]; // 以i结尾的连续的1的长度的期望
double g[N]; // 以i结尾的连续的1的长度的平方的期望
double h[N]; // 直到i若干连续的1的长度的立方和的期望
int main() {
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%lf", &w);
f[i] = w * (f[i - 1] + 1);
g[i] = w * (g[i - 1] + 2 * f[i - 1] + 1);
h[i] = h[i - 1] + w * (3 * g[i - 1] + 3 * f[i - 1] + 1);
}
printf("%.1lf\n", h[n]);
return 0;
}
UVA11021 Tribles麻球繁衍
f[m] 表示第一天有 1 个生物, 到第 m 天全部死亡的概率
则 f[0] = 0, f[m] = p[0] + p[1] * f[m-1] + p[2] * f[m-1]^2 + ... + p[n-1] * f[m-1]^(n-1)
答案为 f[m]^k
点击查看代码
#include <stdio.h>
#include <string.h>
#include <math.h>
const int N = 1005;
double qpow(double b, int p) {
double res = 1;
while(p) {
if(p & 1) res *= b;
b *= b, p >>= 1;
}
return res;
}
int n, m, k;
double p[N], f[N];
int main() {
scanf("%d%d%d", &n, &k, &m);
for(int i = 0; i < n; i ++) scanf("%lf", p + i);
for(int i = 1; i <= m; i ++)
for(int j = 0; j < n; j ++)
f[i] += p[j] * qpow(f[i - 1], j);
printf("%.7lf\n", qpow(f[m], k));
return 0;
}
P1297 [国家集训队]单选错位
点击查看代码
#include <stdio.h> // 结论题: 相邻两个选项相等的情况的概率为 min/prod
int n, A, B, C, a[int(1e7)]; double res; // 这n个相邻的选项的概率相互独立,使用加法原理
int main() {
scanf("%d%d%d%d%d", &n, &A, &B, &C, a);
for(int i = 1; i < n; i ++) a[i] = ((long long)a[i - 1] * A + B) % 100000001;
for(int i = 0; i < n; i ++) a[i] = a[i] % C + 1;
a[n] = a[0];
for(int i = 0; i < n; i ++) res += 1. / (a[i] > a[i + 1] ? a[i] : a[i + 1]);
printf("%.3lf\n", res);
return 0;
}
P4550 收集邮票
用 x 次的花费 = x(x+1)/2
设 a[i] 表示集齐 i 张邮票的次数的期望
设 f[i] 表示集齐 i 张邮票的次数的平方的期望
初始条件 a[n] = f[n] = 0, 答案为 (a[0] + f[0]) / 2
转移: DP
a[i]: 买到买过的 没有买过的
a[i] = (i/n)(a[i]+1) + (1-i/n)(a[i+1]+1)
即 a[i] = a[i+1] + n/(n-i)
f[i]: 买到买过的 没有买过的
f[i] = (i/n)(f[i]+2a[i]+1) + (1-i/n)(f[i+1]+2a[i+1]+1)
即 f[i] = f[i+1] + n/(n-i) + (2i/(n-i))a[i] + 2a[i+1]
点击查看代码
#include <stdio.h>
const int N = 1e4 + 5;
int n; double a[N], f[N];
int main() {
scanf("%d", &n);
for(int i = n - 1; i >= 0; i --) {
a[i] = a[i + 1] + (double)n / (n - i);
f[i] = f[i + 1] + (double)n / (n - i) + 2.0 * i / (n - i) * a[i] + 2.0 * a[i + 1];
}
printf("%.2lf\n", (a[0] + f[0]) / 2.0);
return 0;
}
弱题
f[i][j] 表示操作 i 次后编号为 j 的球的个数的期望, 初始 f[0][i]=a[i]
转移: f[i][j] = f[(i-1)modn][j-1](1/m) + f[i-1][j](1-1/m)
优化: 1. 矩阵快速幂, 每次乘的东西为循环矩阵
2. 使用一维数组存循环矩阵 循环矩阵快速幂:
1-1/m 1/m 0 0 循环矩阵的性质:
0 1-1/m 0 0 A,B为循环矩阵,则A+B和AB为循环矩阵
0 0 1-1/m 1/m 可以只用一维存储,乘法O(n^2)且AB=B*A
1/m 0 0 1-1/m 乘法:下标从0开始时 (i+j)%n <- i,j
点击查看代码
#include <stdio.h>
#include <string.h>
const int N = 1005;
typedef long long LL;
int n, m, k;
double ans[N], base[N];
void Mul(double *c, double *a, double *b) {
static double t[N];
memset(t, 0, sizeof(double) * n);
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
t[(i + j) % n] += a[i] * b[j];
memcpy(c, t, sizeof(double) * n);
}
int main() {
scanf("%d%d%d", &n, &m, &k);
for(int i = 0; i < n; i ++) scanf("%lf", ans + i);
base[0] = 1 - 1. / m, base[1] = 1. / m;
while(k) {
if(k & 1) Mul(ans, ans, base);
Mul(base, base, base), k >>= 1;
}
for(int i = 0; i < n; i ++) printf("%.3lf\n", ans[i]);
return 0;
}
P4507 [CTSC2013]猴子大战
f[i] 表示包含第 i 张牌的胜的概率
f[i] = sum{j!=i}{p[i][j](f[i]+f[j])} / (n-1)
化简得 ((n-1)-sum{j!=i}{p[i][j]}) * f[i] = sum{j!=i}{p[i][j]f[j]}
使用高斯消元, 注意 sum{f[i]}=1
结论: 如果 S中的元素选了, 概率为 sum{i属于S}{f[i]} (证明:多加一个人,分为两队)
点击查看代码
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
const int N = 105;
int n, m;
double a[N][N], x;
void solve() {
int c, r;
for(c = r = 1; c <= n; c ++) {
int t = r;
for(int i = r; i <= n; i ++)
if(fabs(a[i][c]) > fabs(a[t][c])) t = i;
if(fabs(a[t][c]) < 1e-6) continue;
for(int i = c; i <= n + 1; i ++) swap(a[t][i], a[r][i]);
for(int i = n + 1; i >= c; i --) a[r][i] /= a[r][c];
for(int i = r + 1; i <= n; i ++)
if(fabs(a[i][c]) >= 1e-6)
for(int j = n + 1; j >= c; j --)
a[i][j] -= a[r][j] * a[i][c];
r ++;
}
for(int i = n; i; i --)
for(int j = i + 1; j <= n; j ++)
a[i][n + 1] -= a[i][j] * a[j][n + 1];
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i < n; i ++) {
a[i][i] = 1 - n;
for(int j = 1; j <= n; j ++) {
scanf("%lf", &x);
if(i != j) a[i][j] = x, a[i][i] += x;
}
}
for(int j = 1; j <= n; j ++) // 忽略最后一个方程: 可以用前面的构成
scanf("%lf", &x), a[n][j] = 1;
a[n][n + 1] = 1; // sum{f[i]}=1
solve();
for(int i = 0; i < m; i ++) {
static char str[N];
scanf("%s", str + 1), x = 0;
for(int j = 1; j <= n; j ++)
if(str[j] == '1') x += a[j][n + 1];
printf("%.8lf\n", x);
}
return 0;
}
P2059 [JLOI2013] 卡牌游戏
f[i][j] 表示还剩下 i 个人, 从庄主开始数的第 j 个人的获胜的概率
点击查看代码
#include <stdio.h>
const int N = 55;
int n, m, a[N];
double f[N][N];
int main() {
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i ++) scanf("%d", a + i);
f[1][1] = 1;
for(int i = 2; i <= n; i ++)
for(int j = 1; j <= i; j ++)
for(int k = 1; k <= m; k ++) { // 枚举抽出的牌
int v = (a[k] - 1) % i + 1; // 被杀的人的编号
if(v < j) f[i][j] += 1. / m * f[i - 1][j - v]; // 新的编号为 j-(v+1)+1=j-v
if(v > j) f[i][j] += 1. / m * f[i - 1][j - v + i];
}
for(int i = 1; i <= n; i ++) printf("%.2lf%%%c", 100. * f[n][i], " \n"[i==n]);
return 0;
}
点击查看代码
#include <stdio.h> // 结论: n-6个长度为7的区间 都满足 区间为1~7的一个排列的概率 均相同
int a[9];
int main() {
double res = 5040;
for(int i = 1; i <= 7; i ++) scanf("%d", a + i), a[0] += a[i];
for(int i = 1; i <= 6; i ++) res *= a[i], res /= a[0] - i + 1;
printf("%.3lf\n", res * a[7]);
return 0;
}
P4316 绿豆蛙的归宿
期望的性质: E(ax+by)=aE(x)+bE(y)
设 f(i) 为到 i 的期望长度
则 f(i) = ∑ (1/k)(w[i]+f(s[i]))
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int N = 100005, M = 200005;
int n, m;
int h[N], e[M], w[M], nxt[M], idx;
int dout[N];
double f[N];
void add(int a, int b, int c) {
e[++ idx] = b, w[idx] = c, nxt[idx] = h[a], h[a] = idx;
}
// 记忆化搜索
double dfs(int u) {
if(f[u] >= 0) return f[u]; // nan 与任何数比较均返回 false
f[u] = 0;
for(int i = h[u]; i; i = nxt[i]) {
int j = e[i];
f[u] += (w[i] + dfs(j)) / dout[u];
}
return f[u];
}
int main() {
scanf("%d%d", &n, &m);
for(int i = 1, a, b, c; i <= m; i ++) {
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
dout[a] ++;
}
memset(f, -1, sizeof(f)); // nan
printf("%.2lf\n", dfs(1));
return 0;
}
P1291 [SHOI2002] 百事世界杯之旅
已经有 k 种星球, 设 s = k/n, 拿一个新的需要 t 次的概率为 s^{t-1}(1-s)
平均次数 = (1-s) * (1 + 2s + 3s^2 + ...) = 1/(1-s) = n/(n-k)
总平均次数 = n*(1/n + 1/(n-1) + 1/(n-2) + ... + 1/1)
点击查看代码
#include <stdio.h>
typedef __int128_t LLL;
char dig[100];
void write(LLL x) {
int cnt = 0; LLL t;
do t = x / 10, dig[++ cnt] = x - (t << 1) - (t << 3), x = t; while(x);
for(t = cnt; t; t --) putchar(dig[t] ^ 48);
}
LLL gcd(LLL x, LLL y) {
while(x) {
y %= x;
x ^= y ^= x ^= y;
}
return y;
}
int main() {
int n;
scanf("%d", &n);
LLL a = 0, b = 1;
for(int i = 1; i <= n; i ++) {
LLL c = 1;
for(int j = 1; j <= n; j ++)
if(j != i) c *= j;
a += c;
if(i < n) b *= i;
}
if(a % b == 0) write(a / b), putchar(10);
else {
LLL c = a / b, d = a % b, dd = gcd(d, b);
LLL cc = c; int len = 0, len2 = 0;
while(cc) cc /= 10, len ++;
cc = b / dd;
while(cc) cc /= 10, len2 ++;
for(int i = 0; i < len; i ++) putchar(' ');
write(d / dd), putchar(10), write(c);
for(int i = 0; i < len2; i ++) putchar('-');
putchar(10);
for(int i = 0; i < len; i ++) putchar(' ');
write(b / dd), putchar(10);
}
return 0;
}
P1850 NOIP2016提高组 换教室
f[i][j][t]表示考虑到前i节课,已经用了j个申请,第i节课是否用申请(t)的最小期望
点击查看代码
#include <math.h>
#include <stdio.h>
#include <string.h>
int min(int x, int y) { return x < y ? x : y; }
double min(double x, double y) { return x < y ? x : y; }
const int N = 2005, V = 305;
int n, m, v, e, g[V][V];
int id[N][2];
double k[N], f[N][N][2];
int main() {
scanf("%d%d%d%d", &n, &m, &v, &e);
for(int i = 1; i <= n; i ++) scanf("%d", &id[i][0]);
for(int i = 1; i <= n; i ++) scanf("%d", &id[i][1]);
for(int i = 1; i <= n; i ++) scanf("%lf", k + i);
for(int i = 1; i <= v; i ++)
memset(g[i] + 1, 0x3f, v << 2), g[i][i] = 0;
for(int i = 1, a, b, c; i <= e; i ++) {
scanf("%d%d%d", &a, &b, &c);
g[a][b] = g[b][a] = min(g[a][b], c);
}
for(int t = 1; t <= v; t ++)
for(int i = 1; i <= v; i ++)
for(int j = 1; j <= v; j ++)
g[i][j] = min(g[i][j], g[i][t] + g[t][j]);
for(int i = 1; i <= n; i ++)
for(int j = 0; j <= m; j ++) f[i][j][0] = f[i][j][1] = INFINITY;
f[1][0][0] = f[1][1][1] = 0;
for(int i = 2; i <= n; i ++)
for(int j = 0; j <= m; j ++) {
f[i][j][0] = min(
f[i - 1][j][1] + k[i-1] * g[id[i-1][1]][id[i][0]] + (1-k[i-1]) * g[id[i-1][0]][id[i][0]],
f[i - 1][j][0] + g[id[i-1][0]][id[i][0]]
);if(j) f[i][j][1] = min(
f[i - 1][j - 1][1] +
k[i-1]*k[i] * g[id[i-1][1]][id[i][1]] + k[i-1]*(1-k[i]) * g[id[i-1][1]][id[i][0]] +
(1-k[i-1])*k[i] * g[id[i-1][0]][id[i][1]] + (1-k[i-1])*(1-k[i]) * g[id[i-1][0]][id[i][0]],
f[i - 1][j - 1][0] + k[i] * g[id[i-1][0]][id[i][1]] + (1-k[i]) * g[id[i-1][0]][id[i][0]]
);
}
double res = INFINITY;
for(int j = 0; j <= m; j ++) res = min(res, min(f[n][j][0], f[n][j][1]));
printf("%.2lf\n", res);
return 0;
}