一、题目
点此看题
题目描述
j
z
m
jzm
jzm创建了两个号,他每打一场比赛有
p
p
p的概率获胜,有
1
−
p
1-p
1−p的概率失败,胜利积一分,失败扣两分,最高分为
20
20
20分,最低分为
0
0
0分,这为巨佬很聪明,他只会用分更少的号(相同任选)去打比赛,问最后得到一个
20
20
20分的号的期望比赛场数。
数据范围
0.3
≤
p
≤
1
0.3\leq p\leq 1
0.3≤p≤1
二、解法
0x01 高斯消元
这道题的数据范围其实只有
20
20
20,可以考虑暴力高斯消元。
定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]为两个账号达到分达到
i
,
j
i,j
i,j的期望步数(
i
>
j
i>j
i>j),有下列关系式:
d
p
[
i
]
[
j
]
=
p
×
(
d
p
[
i
]
[
j
+
1
]
+
1
)
+
(
1
−
p
)
×
(
d
p
[
i
]
[
j
−
2
]
+
1
)
dp[i][j]=p\times(dp[i][j+1]+1)+(1-p)\times(dp[i][j-2]+1)
dp[i][j]=p×(dp[i][j+1]+1)+(1−p)×(dp[i][j−2]+1)我们把上式变成方程组的形式:
d
p
[
i
]
[
j
]
−
p
×
d
p
[
i
]
[
j
+
1
]
−
(
1
−
p
)
×
d
p
[
i
]
[
j
−
2
]
=
1
dp[i][j]-p\times dp[i][j+1]-(1-p)\times dp[i][j-2]=1
dp[i][j]−p×dp[i][j+1]−(1−p)×dp[i][j−2]=1然后直接暴力消元,时间复杂度
O
(
20
0
3
)
O(200^3)
O(2003)
#include <cstdio>
#include <iostream>
#define eps 1e-7
#define db double
const int M = 305;
using namespace std;
int read()
{
int x=0,flag=1;
char c;
while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x*flag;
}
int n,id[M][M];
db a[M][M],b[M],p;
int cmp(db x,db y)
{
if(x-y>eps) return 1;
if(x-y<-eps) return -1;
return 0;
}
bool gauss(int n,db a[][M])
{
bool flag=1;
for(int i=1; i<=n; i++)
{
int Max=i;
for(int r=i+1; r<=n; r++)
if(cmp(a[Max][i],a[r][i])==-1)
Max=r;
swap(a[i],a[Max]);
if(cmp(a[i][i],0)==0)
{
flag=0;
continue;
}
for(int j=1; j<=n; j++)
{
if(i==j || cmp(a[j][i],0)==0) continue;
for(int k=i+1; k<=n+1; k++)
a[j][k]-=a[i][k]*a[j][i]/a[i][i];
a[j][i]=0;
}
}
return flag;
}
int get(int n,db a[][M],db *b)
{
if(!gauss(n,a))
{
for(int i=1; i<=n; i++)
{
bool flag=1;
for(int j=1; j<=n; j++)
if(cmp(a[i][j],0)!=0)
flag=0;
if(flag && cmp(a[i][n+1],0)!=0)
return -1;
}
return 0;
}
for(int i=1; i<=n; i++)
b[i]=a[i][n+1]/a[i][i];
return 1;
}
int main()
{
for(int i=0; i<20; i++)
for(int j=0; j<=i; j++)
id[i][j]=++n;
while(~scanf("%lf",&p))
{
for(int i=1; i<=n; i++)
for(int j=1; j<=n+1; j++)
a[i][j]=0;
for(int i=0; i<20; i++)
for(int j=0; j<=i; j++)
{
int i1=id[i][j],i2=id[i][j+1],i3=id[i][max(0,j-2)];
if(i==j) i2=id[i+1][j];
a[i1][i1]+=1;
a[i1][i2]-=p;
a[i1][i3]-=(1-p);
a[i1][n+1]=1;
}
get(n,a,b);
printf("%lf\n",b[1]);
}
}
0x02 dp
上面的方法虽说能做此题,但是太暴力了,下面讲一种
O
(
20
)
O(20)
O(20)的方法。
定义
d
p
[
i
]
dp[i]
dp[i]为
i
i
i分到
i
+
1
i+1
i+1分的期望步数,分别考虑赢的和输的情况,有下列转移:
d
p
[
i
]
=
p
+
(
1
−
p
)
×
(
1
+
d
p
[
i
−
2
]
+
d
p
[
i
−
1
]
+
d
p
[
i
]
)
dp[i]=p+(1-p)\times(1+dp[i-2]+dp[i-1]+dp[i])
dp[i]=p+(1−p)×(1+dp[i−2]+dp[i−1]+dp[i])然后我们直接对
d
p
[
i
]
dp[i]
dp[i]移项,这样就得到了一个
d
p
dp
dp方程,我们特判
i
=
0
∣
1
i=0|1
i=0∣1的情况即可,这种方法利用了期望的可加性,最后的答案为
2
∑
d
p
[
i
]
−
d
p
[
19
]
2\sum dp[i]-dp[19]
2∑dp[i]−dp[19],也就是让一个号
20
20
20分,一个号
19
19
19分,详见代码。
思路和代码均来自Stay_Online巨佬,在此感谢。
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <queue>
#include <stack>
#include <map>
#define ull unsigned long long
#define met(a, b) memset(a, b, sizeof(a))
#define lowbit(x) (x&(-x))
#define MID (l + r) / 2
#define ll long long
using namespace std;
const int maxn = 1e5 + 7;
const ll mod = 1e6 + 3;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
double dp[30];
int main() {
double p;
while(cin >> p) {
double sum = 0;//预处理 0 和 1 位置的dp值
dp[0] = 1/p;
dp[1] = (1 + (1 - p) * dp[0]) / p;
sum = dp[0] + dp[1];
for(int i = 2; i <= 19; i++) {
dp[i] = (1 + (1 - p) * (dp[i-2] + dp[i-1])) / p;
sum += dp[i];
}
printf("%.6lf\n", sum+sum-dp[19]); //最后其实两个账号一个为1000分,另一个是950分,所以减去dp【19】。
}
return 0;
}