LightOJ - 1274
题意等价于:由x个1,y个0组成的全排列01字符串
某个位置与前一个位置不同的数量期望,第一个的前面一个数默认位1
思路:先求出x和y
dp[i][j][k]中表示第i个位置位j的,且前面i个数中有k个1的状态到达终点的期望值
答案为dp[0][1][0]
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int N = 5010;
double dp[2][2][N], p1, p2;// k表示yes的数量
int t, n, s, yes, no, l, r, cas;
int main() {
scanf("%d", &t);
while (t--) {
memset(dp, 0, sizeof dp);
scanf("%d%d", &n, &s);
yes = s - 2 * n, no = n - yes;
for (int i = n - 1; i >= 0; i--) {
l = max(0, i - no), r = min(i, yes);
for (int j = l; j <= r; j++)//枚举yes的数量
{
p1 = 1.0 * (no - i + j) / (n - i), p2 = 1.0 * (yes - j) / (n - i);
dp[i & 1][0][j] = dp[!(i & 1)][0][j] * p1 + (dp[!(i & 1)][1][j + 1] + 1) * p2;
dp[i & 1][1][j] = (dp[!(i & 1)][0][j] + 1) * p1 + dp[!(i & 1)][1][j + 1] * p2;
}
}
printf("Case %d: %.7lf\n", ++cas, dp[0][1][0]);
}
return 0;
}
LightOJ - 1287
题意:
给你n(n<15)个点,m条无向边及其权值,你是一个刚偷完东西的小偷,身后有警察追你,因此你需要从1号点开始进行逃亡,在每个点你都有≥1个选择:
1、停留在原地5分钟
2、假设你在u号点,与u节点直接相连的点有v1,v2,…,vx号点,如果其中的某些节点到达它后可以通过这个节点遍历所有的点,那么你就可以选择这个点。选择每个合法点的概率是相等的,求你无路可走的时间期望
思路:
1、对于每个点u(编号0~n-1), 先找出所有可选择的点,假设有k个可选点
2、dp[zt][u]表示经过的点的状态为zt,目前在u点时走完所有点的时间期望(v为u的可选点)
则
化简可得:
3、至于能否走完所有的点,做一下记忆化搜索就可以了
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1 << 16;
int t, n, m, a, b, w, cas;
vector<pii> v[20];
bool vis[N][20], f[N][20];
double dp[N][20];
bool dfs(int st, int u) {
if (st + 1 == 1 << n) {
dp[st][u] = 0;
return 1;
}
if (vis[st][u])return f[st][u];
vis[st][u] = 1;
double &x = dp[st][u] = 0;
int cnt = 0;
for (auto i:v[u]) {
if (!(st & (1 << i.first)) && dfs(st | (1 << i.first), i.first)) {
cnt++;
x += dp[st | 1 << i.first][i.first] + i.second;
f[st][u] = 1;
}
}
if (!f[st][u])return 0;
x = (x + 5) / cnt;
return 1;
}
int main() {
scanf("%d", &t);
while (t--) {
memset(dp, 0, sizeof dp);
memset(vis, 0, sizeof vis);
memset(f, 0, sizeof f);
for (int i = 0; i < 18; i++)v[i].clear();
scanf("%d%d", &n, &m);
while (m--) {
scanf("%d%d%d", &a, &b, &w);
v[a].push_back({b, w});
v[b].push_back({a, w});
}
dfs(1, 0);
printf("Case %d: %.10lf\n", ++cas, dp[1][0]);
}
return 0;
}
牛客练习赛82D
题意等价于:有n个东西,每次选择全部的东西,每个东西有p/q的概率消失
每次需要花费f[n-i+1],求n个东西全部消失的花费期望
F [ i ] = f [ n − i + 1 ] + ∑ k = 0 i C i k ∗ F [ i − k ] ∗ ( p / q ) k ∗ ( 1 − p / q ) i − k F[i]=f[n-i+1]+\sum_{k=0}^iC_i^k*F[i-k]*(p/q)^k*(1-p/q)^{i-k} F[i]=f[n−i+1]+∑k=0iCik∗F[i−k]∗(p/q)k∗(1−p/q)i−k
移项得 ( 1 − ( 1 − p / q ) i − k ) ∗ F [ i ] = ∑ k = 1 i C i k ∗ F [ i − k ] ∗ ( p / q ) k ∗ ( 1 − p / q ) i − k (1-(1-p/q)^{i-k})*F[i]=\sum_{k=1}^iC_i^k*F[i-k]*(p/q)^k*(1-p/q)^{i-k} (1−(1−p/q)i−k)∗F[i]=∑k=1iCik∗F[i−k]∗(p/q)k∗(1−p/q)i−k
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 2010, mod = 998244353;
int n;
ll f[N], F[N], c[N][N], a[N], b[N], p, q;
ll qkpow(ll a, ll b) {
ll sum = 1;
while (b) {
if (b & 1)sum = sum * a % mod;
a = a * a % mod;
b >>= 1;
}
return sum;
}
void init(int n) {
f[1] = 1;
for (int i = 2; i <= n; i++)f[i] = (f[i - 1] + f[i - 2]) % mod;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= i; j++)
if (!j)c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
a[0] = b[0] = 1;
a[1] = p * qkpow(q, mod - 2) % mod, b[1] = (1 - a[1] + mod) % mod;
for (int i = 2; i <= n; i++)a[i] = a[i - 1] * a[1] % mod, b[i] = b[i - 1] * b[1] % mod;
}
int main() {
scanf("%d%lld%lld", &n, &p, &q);
if (p == q)return puts("1"), 0;
init(n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++)
F[i] = (F[i] + c[i][j] * a[j] % mod * b[i - j] % mod * F[i - j]) % mod;
F[i] = (F[i] + f[n - i + 1]) % mod * qkpow((1 - b[i] + mod) % mod, mod - 2) % mod;
}
return printf("%lld\n", F[n]), 0;
}
题意:有个图,节点编号从0到n-1,每条边都有一个概率p表示能成功通过这条边得概率
一个人从0号点开始往n-1号点走,一旦失败就要回到0号点,每次到0号点需要交2k得钱
一共要走通s次
求最终得期望花费
思路:用spfa选一条概率最大的路径走
设走通一次的期望花费为E
则
E
=
p
∗
2
∗
k
+
(
1
−
p
)
∗
(
E
+
2
∗
k
)
E=p*2*k+(1-p)*(E+2*k)
E=p∗2∗k+(1−p)∗(E+2∗k)
化简得
E
=
2
∗
k
/
p
E=2*k/p
E=2∗k/p
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef pair<int, double> pii;
double d[110];
vector<pii> v[110];
bool vis[110];
int t, n, m, a, b, c, k, s, cas;
double spfa() {
memset(d, 0, sizeof d);
memset(vis, 0, sizeof vis);
queue<int> q;
q.push(0);
d[0] = 1;
vis[0] = 1;
while (q.size()) {
int x = q.front();
q.pop();
vis[x] = 0;
for (auto i:v[x]) {
if (d[i.first] < d[x] * i.second) {
d[i.first] = d[x] * i.second;
if (!vis[i.first])q.push(i.first), vis[i.first] = 1;
}
}
}
return d[n - 1];
}
int main() {
cin >> t;
while (t--) {
cin >> n >> m >> s >> k;
for (int i = 0; i < n; i++)v[i].clear();
while (m--) {
cin >> a >> b >> c;
v[a].push_back({b, c / 100.0});
v[b].push_back({a, c / 100.0});
}
printf("Case %d: %.10lf\n", ++cas, 2ll * k * s / spfa());
}
return 0;
}
LightOJ - 1342
题意:有n根木棍,每根木棍有花费,有些是可识别得,剩下的是不可识别的,每次等概率取出一个,如果这个是可识别的,那么不放回,如果是不可识别的就放回,直到每根木棍都至少拿到过一次后的花费期望
dp[i][j]表示已经拿过了i个不可识别的和j个可识别的最小花费
sum表示还剩下多少根木棍,cnt1,cnt2表示这2种木棍的数量,avg1,avg2表示拿出这2
种木棍的期望花费(也就是平均值,因为每根木棍等概率被拿)
d
p
[
i
]
[
j
]
=
(
c
n
t
2
−
i
)
/
s
u
m
∗
(
d
p
[
i
+
1
]
[
j
]
+
a
v
g
2
)
+
(
c
n
t
11
−
j
)
s
u
m
∗
(
d
p
[
i
]
[
j
+
1
]
+
a
v
g
1
)
+
i
/
s
u
m
∗
(
d
p
[
i
]
[
j
]
+
a
v
g
2
)
dp[i][j]=(cnt2-i)/sum*(dp[i+1][j]+avg2)+(cnt11-j)sum*(dp[i][j+1]+avg1)+i/sum*(dp[i][j]+avg2)
dp[i][j]=(cnt2−i)/sum∗(dp[i+1][j]+avg2)+(cnt11−j)sum∗(dp[i][j+1]+avg1)+i/sum∗(dp[i][j]+avg2)
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int t, n, a, b, cas;
double dp[2][5010];
int main() {
scanf("%d", &t);
while (t--) {
memset(dp, 0, sizeof dp);
scanf("%d", &n);
int val1 = 0, val2 = 0, cnt1 = 0, cnt2 = 0;
for (int i = 1; i <= n; i++) {
scanf("%d%d", &a, &b);
if (b == 1)cnt1++, val1 += a;
else cnt2++, val2 += a;
}
double avg1 = 0, avg2 = 0;
if (cnt1)avg1 = 1.0 * val1 / cnt1;
if (cnt2)avg2 = 1.0 * val2 / cnt2;
for (int i = cnt2; i >= 0; i--)
for (int j = cnt1; j >= 0; j--)
if (i != cnt2 || j != cnt1) {
dp[i & 1][j] = dp[!(i & 1)][j] * (cnt2 - i) / (n - j) +
(dp[i & 1][j + 1] + avg1) * (cnt1 - j) / (n - j) +
avg2 * cnt2 / (n - j);
dp[i & 1][j] *= 1.0 * (n - j) / (n - i - j);
}
printf("Case %d: %.10lf\n", ++cas, dp[0][0]);
}
return 0;
}
LightOJ - 1030
题意:有个1*n的网格,1为起点,n为终点,每个位置都有若干的金币
每次投一个1~6均匀分布的骰子,投到多少前进多少步,并获得那个位置的金币,若此次行走会超过终点,那么重新投,直到刚好走到n位置,求获得金币的期望
d p [ i ] = 1 / 6 ∗ ( ∑ j = i + 1 i + 6 d p [ j ] + a [ j ] ) dp[i]=1/6*(\sum_{j=i+1}^{i+6}dp[j]+a[j]) dp[i]=1/6∗(∑j=i+1i+6dp[j]+a[j])
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int t, n, a[110], cas;
double dp[110];
int main() {
scanf("%d", &t);
while (t--) {
memset(dp, 0, sizeof dp);
scanf("%d", &n);
for (int i = 1; i <= n; i++)scanf("%d", &a[i]);
for (int i = n - 1; i >= 1; i--) {
int cnt = 0;
for (int j = i + 1; j <= min(n, i + 6); j++) {
cnt++;
dp[i] += dp[j] + a[j];
}
dp[i] /= cnt;
}
printf("Case %d: %.10lf\n", ++cas, dp[1] + a[1]);
}
return 0;
}
LightOJ - 1364
题意:有54张扑克牌其中2张小丑,黑桃、红桃、梅花和方块各13张,每次拿走一张,若拿到小丑,随机将小丑变成黑桃、红桃、梅花或者方块中的一张,直到拿到手的牌中黑桃的数量大于等于A,红桃的数量大于等于B,梅花的数量大于等于C,方块的数量大于等于D时停止,求拿牌的期望数量
a,b,c,d表示黑桃、红桃、梅花和方块的数量,x表示其中一张小丑的状态y表示另一张小丑的状态
0~3表示黑桃、红桃、梅花和方块,4表示还未拿到手
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int inf = 0x3f3f3f3f;
int t, A, B, C, D, cas;
double dp[14][14][14][14][5][5];
double dfs(int a, int b, int c, int d, int x, int y) {
double &v = dp[a][b][c][d][x][y];
if (v >= 0)return v;
int aa = a + (x == 0) + (y == 0);
int bb = b + (x == 1) + (y == 1);
int cc = c + (x == 2) + (y == 2);
int dd = d + (x == 3) + (y == 3);
if (aa >= A && bb >= B && cc >= C && dd >= D)return v = 0;
int sum = aa + bb + cc + dd;
if (sum >= 54)return v = inf;
v = 1;
if (a < 13)v += 1.0 * (13 - a) / (54 - sum) * dfs(a + 1, b, c, d, x, y);
if (b < 13)v += 1.0 * (13 - b) / (54 - sum) * dfs(a, b + 1, c, d, x, y);
if (c < 13)v += 1.0 * (13 - c) / (54 - sum) * dfs(a, b, c + 1, d, x, y);
if (d < 13)v += 1.0 * (13 - d) / (54 - sum) * dfs(a, b, c, d + 1, x, y);
if (x == 4) {
double t = inf;
for (int i = 0; i < 4; i++)
t = min(t, dfs(a, b, c, d, i, y) / (54 - sum));
v += t;
}
if (y == 4) {
double t = inf;
for (int i = 0; i < 4; i++)
t = min(t, dfs(a, b, c, d, x, i) / (54 - sum));
v += t;
}
return v;
}
int main() {
cin >> t;
while (t--) {
memset(dp, -1, sizeof dp);//nan
cin >> A >> B >> C >> D;
double ans = dfs(0, 0, 0, 0, 4, 4);
printf("Case %d: ", ++cas);
if (ans >= inf)puts("-1");
else printf("%.10lf\n", ans);
}
return 0;
}
LightOJ - 1395
题意:有n扇门,每个门有一个花费,若为正数表示结束游戏,最多能记住k扇门,使得选择门时不考虑他们,求结束游戏的期望花费
f[i]表示记住了i扇门的期望
cnta表示正数的个数,cntb表示负数的个数
avga表示正数门的均值,abgb表示负数门的均值
先对
k
=
m
i
n
(
k
,
c
n
t
b
)
k=min(k,cntb)
k=min(k,cntb)
终点为
f
[
k
]
=
(
c
n
t
b
−
k
)
/
(
n
−
k
)
∗
(
f
[
k
]
+
a
v
g
b
)
+
a
/
(
n
−
k
)
∗
a
v
g
a
f[k]=(cntb-k)/(n-k)*(f[k]+avgb)+a/(n-k)*avga
f[k]=(cntb−k)/(n−k)∗(f[k]+avgb)+a/(n−k)∗avga
中间过程为
f
[
i
]
=
(
b
−
i
)
/
(
n
−
i
)
∗
(
f
[
i
+
1
]
+
a
v
g
b
)
+
a
/
(
n
−
i
)
∗
a
v
g
a
f[i]=(b-i)/(n-i)*(f[i+1]+avgb)+a/(n-i)*avga
f[i]=(b−i)/(n−i)∗(f[i+1]+avgb)+a/(n−i)∗avga
在化简即可
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int t, n, k, cas, x;
double dp[110];
int main() {
scanf("%d", &t);
while (t--) {
int cnta = 0, cntb = 0, suma = 0, sumb = 0;
memset(dp, 0, sizeof dp);
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &x);
if (x > 0)suma += x, cnta++;
else sumb -= x, cntb++;
}
double avga = 0, avgb = 0;
printf("Case %d: ", ++cas);
if (cnta)avga = 1.0 * suma / cnta;
else {
puts("-1");
continue;
}
if (cntb)avgb = 1.0 * sumb / cntb;
k = min(k, cntb);
dp[k] = avgb * (cntb - k) / (n - cntb) + avga * cnta / (n - cntb);
for (int i = k - 1; i >= 0; i--)
dp[i] = (dp[i + 1] + avgb) * (cntb - i) / (n - i) + avga * cnta / (n - i);
printf("%.10lf\n", dp[0]);
}
return 0;
}
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
const double eps = 1e-7;
int t;
double c, m, p, v;
double dp(double c, double m, double p) {
double res = 0;
if (c > eps) {
double x = min(c, v);
if (m > eps && p > eps)res += c * (dp(c - x, m + x / 2, p + x / 2) + 1);
else if (m > eps)res += c * (dp(c - x, m + x, p) + 1);
else res += c * (dp(c - x, m, p + x) + 1);
}
if (m > eps) {
double x = min(m, v);
if (c > eps && p > eps)res += m * (dp(c + x / 2, m - x, p + x / 2) + 1);
else if (c > eps)res += m * (dp(c + x, m - x, p) + 1);
else res += m * (dp(c, m - x, p + x) + 1);
}
return res;
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%lf%lf%lf%lf", &c, &m, &p, &v);
printf("%.10lf\n", 1.0 + dp(c, m, p));
}
return 0;
}
LightOJ - 1408
题意:投丢的概率为p,投中的概率为1-p
若连续投中k1个或连续投丢k2个游戏结束
求投球次数期望
f[i]: 已经连续投中 i 次,距离游戏结束还需投球次数的期望.
g[i]:已经连续投空 i 次,距离游戏结束还需投球次数的期望.
易得转移方程:
f [ i ] = ( 1 − p ) ∗ f [ i + 1 ] + p ∗ g [ 1 ] + 1. f[i] = (1 - p) * f[i + 1] + p * g[1] + 1. f[i]=(1−p)∗f[i+1]+p∗g[1]+1.
g [ i ] = p ∗ g [ i + 1 ] + ( 1 − p ) ∗ f [ 1 ] + 1. g[i] = p * g[i + 1] + (1 - p) * f[1] + 1. g[i]=p∗g[i+1]+(1−p)∗f[1]+1.
经简单迭代可得:
联立两式可得:
解出 f[1] 后回带即可得到 g[1].
最终答案 ans = qf[1] + pg[1] + 1.
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
const double eps = 1e-7;
int t, k1, k2, cas;
double p, f, g;
double qkpow(double a, int b) {
double sum = 1;
while (b) {
if (b & 1)sum *= a;
a *= a;
b >>= 1;
}
return sum;
}
int main() {
scanf("%d", &t);
while (t--) {
scanf("%lf%d%d", &p, &k1, &k2);
double q = 1 - p;
printf("Case %d: ", ++cas);
if (p < eps) {
if (k2 == 0)printf("0\n");
else printf("%.10lf\n", 1.0 * k1);
continue;
}
if (q < eps) {
if (k1 == 0)printf("0\n");
else printf("%.10lf\n", 1.0 * k2);
continue;
}
double qk = qkpow(q, k1 - 1), pk = qkpow(p, k2 - 1);
f = (1 - qk) * ((1 - pk) / q + 1 / p) / (1 - (1 - pk) * (1 - qk));
g = ((1 - p) * f + 1) * (1 - pk) / q;
printf("%.10lf\n", q * f + p * g + 1);
}
return 0;
}