动态规划专题 - 解题报告 - K

首先想到靠一次抽中和靠收集碎片得到角色得分开计算。
n次里靠一次抽中的概率为: a n s 1 = 1 − ( 1 − x ) n ans1 =1-(1-x)^n ans1=1(1x)n
而靠收集碎片则需要一个递推公式,我们令dp[ i ][ j ]为抽i次抽到j种碎片的概率,是由以下两种情况转移而来的

  1. i-1次抽奖时已经抽到了j种碎片dp[i - 1][j],那么再抽一次,要么什么都抽不到,要么出的碎片还是之前出现过的碎片,概率为 ( 1 − x − m ⋅ y ) + j ⋅ y (1-x-m\cdot y) +j\cdot y (1xmy)+jy
  2. 另一种是在第i-1次抽奖时抽到了j-1种碎片dp[i - 1][j - 1],那么这次就是在剩下的没有抽到的(m-j-1)种碎片中抽到一种即可,概率为 ( m − j + 1 ) ⋅ y (m-j+1)\cdot y (mj+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]=1x(mj)ydp[i1][j]+(mj+1)ydp[i1][j1]
得到 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(1000x)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]=1000x(mj)ydp[i1][j]+(mj+1)ydp[i1][j1]
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值