概率期望小结

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) + (2
i/(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)且A
B=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;
}

####[P3802 小魔女帕琪](https://www.luogu.com.cn/problem/P3802)
点击查看代码
#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;
}
posted @ 2022-09-28 15:46  azzc  阅读(48)  评论(0编辑  收藏  举报