首先想到靠一次抽中和靠收集碎片得到角色得分开计算。
n次里靠一次抽中的概率为:
a
n
s
1
=
1
−
(
1
−
x
)
n
ans1 =1-(1-x)^n
ans1=1−(1−x)n
而靠收集碎片则需要一个递推公式,我们令dp[ i ][ j ]为抽i次抽到j种碎片的概率,是由以下两种情况转移而来的
- i-1次抽奖时已经抽到了j种碎片dp[i - 1][j],那么再抽一次,要么什么都抽不到,要么出的碎片还是之前出现过的碎片,概率为 ( 1 − x − m ⋅ y ) + j ⋅ y (1-x-m\cdot y) +j\cdot y (1−x−m⋅y)+j⋅y
- 另一种是在第i-1次抽奖时抽到了j-1种碎片dp[i - 1][j - 1],那么这次就是在剩下的没有抽到的(m-j-1)种碎片中抽到一种即可,概率为 ( m − j + 1 ) ⋅ y (m-j+1)\cdot y (m−j+1)⋅y
所以就有状态转移方程:
d
p
[
i
]
[
j
]
=
(
1
−
x
−
(
m
−
j
)
⋅
y
)
⋅
d
p
[
i
−
1
]
[
j
]
+
(
m
−
j
+
1
)
⋅
y
⋅
d
p
[
i
−
1
]
[
j
−
1
]
dp[i][j]=(1-x-(m-j)\cdot y)\cdot dp[i - 1][j] + (m-j+1)\cdot y\cdot dp[i-1][j-1]
dp[i][j]=(1−x−(m−j)⋅y)⋅dp[i−1][j]+(m−j+1)⋅y⋅dp[i−1][j−1]
得到
a
n
s
2
=
d
p
[
n
]
[
m
]
ans2 = dp[n][m]
ans2=dp[n][m]
最后答案就是ans1+ans2。
但是我一度以为我推的式子是错的或者说这个题是不可做的,这样的浮点运算怎么看都会有精度误差,于是鸽了好久都没碰这题。直到醒悟过来这题给的三位小数和1000的n次方要怎么用,因为不管是ans1求幂还是ans2的递推,乘法运算都有n次,每次都乘上一个1000,不就是一个整数了??
也就是说,这题本就没打算让我们用浮点数计算,读入后处理将x和y变成long long类型的长整数直接计算。
重写一遍公式:
a
n
s
1
=
100
0
n
−
(
1000
−
x
)
n
ans1 =1000^n-(1000-x)^n
ans1=1000n−(1000−x)n
d
p
[
i
]
[
j
]
=
(
1000
−
x
−
(
m
−
j
)
⋅
y
)
⋅
d
p
[
i
−
1
]
[
j
]
+
(
m
−
j
+
1
)
⋅
y
⋅
d
p
[
i
−
1
]
[
j
−
1
]
dp[i][j]=(1000-x-(m-j)\cdot y)\cdot dp[i - 1][j] + (m-j+1)\cdot y\cdot dp[i-1][j-1]
dp[i][j]=(1000−x−(m−j)⋅y)⋅dp[i−1][j]+(m−j+1)⋅y⋅dp[i−1][j−1]
a
n
s
2
=
d
p
[
n
]
[
m
]
ans2 = dp[n][m]
ans2=dp[n][m]
然后就是一般操作了,过大的数据范围要求矩阵加速来优化递推,写出矩阵如下:
而我们有初始状态dp[1][0] = 1 - x - m * y;dp[1][1] = m * y;
所以求出(n-1)次方后,有
ll ans2 = ((1000 - x - m * y) * after_power.m[m][0] % hrdg + (m * y) * after_power.m[m][1] % hrdg) % hrdg;
即为ans2完整操作
一点点优化:
我们知道n<m时抽碎片不肯集满换角色的,所以提前判断,如果n<m,直接打印ans1并结束程序,与后面的矩阵构造和计算说拜拜。这种一剪一大坨的优化,虽然可以有不加的必要但还是好爽啊。
好像这样的特判还能躲过n==0的毒瘤数据。
AC代码:
#include<bits/stdc++.h>
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define maxn 105
#define maxm 55
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
#define id(a, b) (a-1)*(m+1)+b
using namespace std;
int n, m;
double xp, yp;
ll x, y;
inline int read(){
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
struct mat //结构体存矩阵
{
ll m[maxn][maxn];
}a, e;
mat mul(mat a, mat b) //矩阵乘法
{
mat c;
FOR(i, 0, m)
FOR(j, 0, m)
c.m[i][j] = 0;
FOR(i, 0, m)
FOR(j, 0, m)
FOR(k, 0, m)
c.m[i][j] = (c.m[i][j] % hrdg + a.m[i][k] * b.m[k][j] % hrdg) % hrdg;
return c;
}
mat matpow(mat a, ll x) //矩阵快速幂,其实跟普通快速幂一样的
{
mat ret = e;
while(x)
{
if(x&1)
ret = mul(ret, a);
a = mul(a, a);
x>>=1;
}
return ret;
}
ll Qpow(ll a, int x) //快速幂
{
ll ret = 1;
while(x)
{
if(x & 1)
ret = ret * a % hrdg;
a = a * a % hrdg;
x >>= 1;
}
return ret % hrdg;
}
int main()
{
n = read();
m = read();
scanf("%lf %lf", &xp, &yp);
xp = xp * 1000; x = (ll)xp;
yp = yp * 1000; y = (ll)yp; //转化为长整形变量
ll ans1 = (Qpow(1000, n) - Qpow((1000 - x), n) + hrdg) % hrdg;
if(n < m) //特判
{
printf("%lld", ans1);
exit(0);
}
memset(a.m, 0, sizeof(a.m));
memset(e.m, 0, sizeof(e.m));
FOR(i, 0, m)
e.m[i][i] = 1; //单位矩阵
FOR(i, 0, m)
a.m[i][i] = 1000 - x - (m - i) * y;
FOR(i, 1, m)
a.m[i][i - 1] = (m - i + 1) * y; //初始矩阵
mat after_power = matpow(a, n - 1); //矩阵快速幂运算
ll ans2 = ((1000 - x - m * y) * after_power.m[m][0] % hrdg + (m * y) * after_power.m[m][1] % hrdg) % hrdg;
ll ANS = (ans1 + ans2) % hrdg;
cout << ANS;
return 0;
}