题目
飞行棋的规则如下:
1、每名玩家有一个棋子,每个回合可以掷一次骰子。
2、如果使用的骰子为 k面,则这 k面上的点数分别为 1,2,3,…,k,且掷得每种点数的概率均为。
3、如果当前回合掷得的点数为 Q,则玩家控制的棋子前进 Q 步。
4、若当前棋子的位置到终点的距离 d < Q,则棋子先行动 d 步到终点,再倒退 Q-d 步。(即到终点的距离变为 Q−d)
5、某一回合结束后,若该玩家的棋子恰好到达终点,则宣布胜利。
璇璇姐姐参与了这个游戏,已知现在她的棋子到终点的距离为 d,
游戏使用的骰子有 k面,求璇璇获得胜利需要的回合数期望。
保证 1<=k<=20,1<=d<=1e18,且k<=d。
思路来源
fls
题解
考虑距离终点k步及以内的情形,dp[i]代表从i这个点,走到终点的期望数
那么dp[1],掷到一步获胜,掷到两步回到1的位置,掷到三步到2的位置,掷到k步去k-1的位置
有,其中dp[0]=0
类似地有,
可以列出所有方程,
可以发现,由于右边每项括号里都有k-1项非零值,高度对称,
所以可以构造一组全相等的解,代入x有x=k
后面的仍用搞矩阵快速幂即可
学到了把k+1阶向量也搞成(k+1)*1的矩阵技巧,
这样最后求第n项时,再搞一个矩阵乘法即可
代码
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const ll MOD = 1e9+7;
const int MAXN = 22;
ll d,k,inv,ans;
ll modpow(ll x,ll n,ll mod)
{
ll res=1;
for(;n;n/=2,x=x*x%mod)
if(n&1)res=res*x%mod;
return res;
}
struct mat {
ll c[MAXN][MAXN];
int m, n;
mat(){
memset(c, 0, sizeof(c));
m=n=MAXN;
}
mat(int a, int b) : m(a), n(b) {
memset(c, 0, sizeof(c));
}
void clear(){
memset(c, 0, sizeof(c));
}
mat operator * (const mat& temp) {
mat ans(m, temp.n);
for (int i = 0; i < m; i ++)
for (int j = 0; j < temp.n; j ++)
{
for (int k = 0; k < n; k ++)
{
ans.c[i][j] += c[i][k] * temp.c[k][j]%MOD;//能不取模 尽量不取模
//这里maxn=2 故不会超过ll 视具体情况 改变取模情况
if(ans.c[i][j]>=MOD)ans.c[i][j]%=MOD;
}
}
return ans;
}
friend mat operator ^(mat M, ll n)
{
mat ans(M.m, M.m);
for (int i = 0; i < M.m; i ++)
ans.c[i][i] = 1;
while (n > 0) {
if (n & 1) ans = ans * M;
M = M * M;
n >>= 1;
}
return ans;
}
};
int main()
{
scanf("%lld%lld",&d,&k);
mat a(k+1,k+1),b(k+1,1);
inv=modpow(k,mod-2,mod);
for(int i=0;i<k;++i)
a.c[0][i]=inv;
a.c[0][k]=1;
for(int i=1;i<k;++i)
a.c[i][i-1]=1;
a.c[k][k]=1;
a=a^(d-k);
for(int i=0;i<k;++i)
b.c[i][0]=k;
b.c[k][0]=1;
a=a*b;
printf("%lld\n",a.c[0][0]);
return 0;
}