CCPC-Wannafly & Comet OJ 夏季欢乐赛(2019)E.飞行棋(期望dp+矩阵快速幂)

题目

飞行棋的规则如下:

1、每名玩家有一个棋子,每个回合可以掷一次骰子。

2、如果使用的骰子为 k面,则这 k面上的点数分别为 1,2,3,…,k,且掷得每种点数的概率均为\frac{1}{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[1]=\frac{1}{k}(dp[0]+dp[1]+...+dp[k-1])+1,其中dp[0]=0

类似地有,dp[k]=\frac{1}{k}(dp[0]+dp[1]+...+dp[k-1])+1

可以列出所有方程,

dp[1]=\frac{1}{k}(dp[0]+dp[1]+...+dp[k-1])+1

dp[2]=\frac{1}{k}(dp[0]+2*dp[1]+dp[2]+...+dp[k-2])+1

dp[3]=\frac{1}{k}(dp[0]+2*dp[1]+2*dp[2]+dp[3]...+dp[k-3])+1

可以发现,由于右边每项括号里都有k-1项非零值,高度对称,

所以可以构造一组全相等的解dp[1]=dp[2]=...=dp[k]=x,代入x有x=k

后面的仍用dp[n]=\frac{1}{k}(dp[n-k]+dp[n-k+1]+...+dp[n-1])+1搞矩阵快速幂即可

 

学到了把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;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小衣同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值