[BZOI2018]治疗之雨

房东的猫 提交于 2020-03-21 10:50:10

题面:Luogu
题解:期望dp+gauss消元
先吐槽一下这个题面,根本看不懂,看讨论区半天才发现,另外m个就是用来降低概率的
\(prob_i\)表示一轮降了\(i\)点的概率
\[Prob_i=\frac{\binom{k}{i}m^{k-i}}{(m+1)^k}\]
总共\((m+1)^{k}\)种方案,选\(i\)次降一点,剩下的不降
由于\(k\)很大,所以\(\binom{k}{i}\)不能通过预处理阶乘算出,而要递推(否则会#6~#10RE)
递推方法很多,我的是
\[prob_i=\frac{1}{i!}*m^{k-i}*\frac{k!}{(k-i)!}*\frac{1}{(m+1)^k}\]
\(Q_{x,y}\)表示一轮操作从\(x\)点变成\(y\)
\[Q_{x,y}=\begin{split} \left\{ \begin{array}{lr} 0 & x=n \&\& y=n+1 &\\ Prob_{x-y} & x=n\\ \frac{1}{m+1}Prob_0 & y=x+1\\ \frac{m}{m+1}Prob_{x-y}+\frac{1}{m+1}Prob_{x-y+1} & \text{otherwise} \end{array} \right. \end{split} \]
\(dp_i\)表示从\(i\)点变成\(0\)的期望步数
\[ \begin{split} & dp_i=1+\sum_{j=1}^{i+1} Q_{i,j}dp_j & (1\le i<n) \\ & dp_n=1+\sum_{j=1}^nQ_{n,j}dp_j \end{split} \]
于是可以推一下式子
\[dp_{i+1}=\frac{dp_i-1-\sum_{j=1}^{i}{Q_{i,j}dp_{j}}}{Q_{i,i+1}} (1\le i<n)\]
可以发现,我们只需求出\(dp_1\)即可求出其它
然后是两种办法
1.高斯消元
矩阵大概是这样
\[ \left[ \begin{matrix} 1-Q_{1,1} & -Q_{1,2} & 0 &\cdots & 0\\ -Q_{2,1} & 1-Q_{2,2} & -Q_{2,3} &\cdots & 0 \\ \vdots & \vdots & \vdots &\ddots & \vdots \\ -Q_{n-1,1} & -Q_{n-1,2} & -Q_{n-1,3} &\cdots & -Q_{n-1,n} \\ -Q_{n,1} & -Q_{n,2} & -Q_{n,3} &\cdots & 1-Q_{n,n} \end{matrix} \right]* \left[ \begin{matrix} dp_{1}&\\ dp_{2}&\\ \vdots&\\ dp_{n-1}&\\ dp_{n}& \end{matrix} \right]= \left[ \begin{matrix} 1&\\ 1&\\ \vdots&\\ 1&\\ 1& \end{matrix} \right] \]
然后可以通过一些手法把它搞成只要\(n^2\)

2.设\(dp_1=X\)
则每一项都可以表示为\(A[i]*X+B[i]\)
然后通过\(dp_n\)的递推式也能得到一个\(dp_n=An*X+Bn\)
所以
\[A[n]*X+B[n]=An*X+Bn \\ X=\frac{Bn-B[n]}{A[n]-An} \]
然后带回去就可以了
我也是写的这种方法
还有一件令我迷惑的事:prob每次不清零会WA#1~#5,但是我写的每次应该都会重新算一遍,在没被改变的地方应该也没有被调用

#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T& x)
{
    x = 0; char c = getchar();
    while (!isdigit(c)) c = getchar();
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
#define maxn 1505
#define P 1000000007
int fac[maxn], inv[maxn], prob[maxn];
int A[maxn], B[maxn], An, Bn, X;
int n, p, m, k, invM;
inline int mul(int x, int y) { return 1ll * x * y % P; }
inline int qpow(int x, int y)
{
    int ans = 1;
    for (; y; x = mul(x, x), y >>= 1) if (y & 1) ans = mul(ans, x);
    return ans;
}
inline int getp(int x, int y)//Q_{x,y}
{
    if (x == n && y == n + 1) return 0;
    if (x == n) return prob[x - y];
    if (y == x + 1) return mul(invM, prob[0]);
    return (0ll + mul(mul(m, prob[x - y]), invM) + mul(invM, prob[x - y + 1])) % P;
}
inline void exgcd(int a, int b, int& x, int& y)
{
    if (!b) { x = 1, y = 0; return; }
    exgcd(b, a % b, y, x);
    y -= a / b * x;
}
inline int getinv(int a)
{
    int x, y;
    exgcd(a, P, x, y);
    x %= P;
    return (x < 0) ? x + P : x;
}
inline void init(int maxnum = 1500)
{
    fac[0] = inv[0] = 1;
    for (int i = 1; i <= maxnum; ++i) fac[i] = 1ll * fac[i - 1] * i % P;
    inv[maxnum] = getinv(fac[maxnum]);
    for (int i = maxnum - 1; i; --i) inv[i] = 1ll * inv[i + 1] * (i + 1) % P;
}
inline void getprob()
{
    invM = getinv(m + 1);
    int inv1 = qpow(invM, k);
    for (int i = 0, tp = qpow(m, k), tp2 = getinv(m), tpm = 1; i <= min(n, k); ++i)
        prob[i] = mul(mul(mul(inv[i], tpm), tp), inv1), tp = mul(tp, tp2), tpm = mul(tpm, k - i);
    //tp=m^{k-i},inv[i]=\frac{1}{i!},tpm=\frac{k!}{(k-i)!},inv1=\frac{1}{(m+1)^k}
}
inline void solve()
{
    A[1] = 1, B[1] = 0; An = 0, Bn = 1;
    for (int i = 2; i <= n; ++i)
    {
        A[i] = A[i - 1], B[i] = B[i - 1] - 1;
        for (int j = 1, tp; j < i; ++j)
        {
            tp = getp(i - 1, j);
            A[i] = (0ll + A[i] - mul(tp, A[j])) % P;
            B[i] = (0ll + B[i] - mul(tp, B[j])) % P;
        }
        if (A[i] < 0) A[i] += P;
        if (B[i] < 0) B[i] += P;
        int tp = getinv(getp(i - 1, i));
        A[i] = mul(A[i], tp), B[i] = mul(B[i], tp);
    }
    for (int i = 1, tp; i <= n; ++i)
    {
        tp = getp(n, i);
        An = (0ll + An + mul(tp, A[i])) % P;
        Bn = (0ll + Bn + mul(tp, B[i])) % P;
    }
    X = mul((Bn - B[n] + P) % P, getinv((A[n] - An + P) % P));
    printf("%d\n", (mul(A[p], X) + B[p]) % P);
    memset(prob, 0, sizeof(prob));
}
int main()
{
    int T; read(T);
    init();
    while (T--)
    {
        read(n), read(p), read(m), read(k);
        if (!k || (!m && k == 1)) { puts("-1"); continue; }//特判
        if (!m)
        {
            int ans = 0;
            while (p > 0)
            {
                if (p < n) ++p;
                p -= k, ++ans;
            }
            printf("%d\n", ans);
            continue;
        }
        getprob();
        solve();
    }
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!