bzoj5292: [Bjoi2018]治疗之雨【期望dp】

Description

你现在有m+1个数:第一个为p,最小值为0,最大值为n;剩下m个都是无穷,没有最小值或最大值。
你可以进行任意多轮操作,每轮操作如下:
在不为最大值的数中等概率随机选择一个(如果没有则不操作),把它加一;
进行k次这个步骤:在不为最小值的数中等概率随机选择一个(如果没有则不操作),把它减一。
现在问期望进行多少轮操作以后第一个数会变为最小值0。

Input

输入包含多组数据。
输入第一行包含一个正整数T,表示数据组数。
接下来T行,每行4个非负整数n、p、m、k(含义见题目描述),表示一次询问。
1≤T≤100
1≤p≤n≤1500
0≤m,k≤1000000000 。
保证不存在n=p=k=1 ,m=0 的情况(因为出题人判错了)
保证不存在答案的分母是1000000007 的倍数的情况(因为出题人没想到)

Output

输出T行,每行一个整数,表示一次询问的答案。
如果无论进行多少轮操作,第一个数都不会变为最小值0,那么输出-1;
否则,可以证明答案一定为有理数,那么请输出答案模1000000007的余数,
即设答案为b/a(a、b为互质的正整数),你输出的整数为x,
那么你需要保证0≤x<1000000007且a≡bx mod 1000000007

Sample Input

2

2 1 1 1

2 2 1 1

Sample Output

6

8

解题思路:

首先设 f[i] f [ i ] k k 次攻击击中英雄 i i 次的概率,即 f[i]=(ki)(1m+1)i(mm+1)ki f [ i ] = ( k i ) ( 1 m + 1 ) i ( m m + 1 ) k − i

dp[i] d p [ i ] 为还剩 i i 滴血时期望多少轮挂掉,则 dp[0]=0 d p [ 0 ] = 0
1<i<n 1 < i < n

dp[i]=1m+1j=0if[j]dp[i+1j]+mm+1j=0if[j]dp[ij]+1 d p [ i ] = 1 m + 1 ∑ j = 0 i f [ j ] d p [ i + 1 − j ] + m m + 1 ∑ j = 0 i f [ j ] d p [ i − j ] + 1
i=n i = n
dp[i]=j=0if[j]dp[ij]+1 d p [ i ] = ∑ j = 0 i f [ j ] d p [ i − j ] + 1

利用第一个式子,我们可以从 dp[i] d p [ i ] dp[i+1] d p [ i + 1 ] ,但 dp[1] d p [ 1 ] 是不知道的,所以可以设 dp[1]=x d p [ 1 ] = x ,那么 dp[i] d p [ i ] 皆可以表示成 Ax+B A x + B 的形式,在结合第二个式子就可以解出 x x 了。

注意 m=0 m = 0 要特判。

时间复杂度 O(Tn2) O ( T n 2 )

#include
#define ll long long
using namespace std;
int getint()
{
    int i=0,f=1;char c;
    for(c=getchar();(c!='-')&&(c<'0'||c>'9');c=getchar());
    if(c=='-')c=getchar(),f=-1;
    for(;c>='0'&&c<='9';c=getchar())i=(i<<3)+(i<<1)+c-'0';
    return i*f;
}
const int N=1505,mod=1e9+7;
ll n,p,m,k,f[N];
inline ll add(ll x,ll y){return x+y>mod?x+y-mod:x+y;}
struct data
{
    ll x,y;
    data(ll _x=0,ll _y=0):x(_x),y(_y){}
    inline data operator + (const data &b){return data(add(x,b.x),add(y,b.y));}
    inline data operator - (const data &b){return data(add(x,-b.x+mod),add(y,-b.y+mod));}
    inline data operator * (const ll &b){return data(x*b%mod,y*b%mod);}
}dp[N];
ll Pow(ll x,ll y)
{
    ll res=1;
    for(;y;y>>=1,x=x*x%mod)
        if(y&1)res=res*x%mod;
    return res;
}
ll solve1()
{
    if(k==1&&n!=1)return -1;
    f[0]=0;
    for(int i=1;i1-k,0ll)]+1;
    f[n]=f[max(n-k,0ll)]+1;
    return f[p];
}
ll solve()
{
    n=getint(),p=getint(),m=getint(),k=getint();
    if(k==0)return -1;
    if(!m)return solve1();
    ll invm=Pow(m,mod-2),x=min(k,n);
    f[0]=Pow(m*Pow(m+1,mod-2)%mod,k);if(f[0]==0||f[0]==1)return -1;
    for(int i=1;i<=x;i++)f[i]=f[i-1]*(k-i+1)%mod*Pow(i,mod-2)%mod*invm%mod;
    ll finv=Pow(f[0],mod-2);
    dp[0]=data(0,0),dp[1]=data(1,0);
    for(int i=1;i1]=data(dp[i].x,dp[i].y-1)*(m+1);
        x=min((ll)i,k);
        for(int j=0;j<=x;j++)dp[i+1]=dp[i+1]-(dp[i-j]*f[j])*m;
        x=min((ll)i+1,k);
        for(int j=1;j<=x;j++)dp[i+1]=dp[i+1]-dp[i+1-j]*f[j];
        dp[i+1]=dp[i+1]*finv;
    }
    data t=data(0,1);x=min(n,k);
    for(int j=1;j<=x;j++)t=t+dp[n-j]*f[j];
    t=t*Pow(1-f[0]+mod,mod-2),t=t-dp[n];
    if(t.x==0&&dp[p].x)return -1;
    ll X=0;if(t.x)X=(mod-t.y)*Pow(t.x,mod-2)%mod;
    return (dp[p].x*X%mod+dp[p].y)%mod;
}
int main()
{
    //freopen("heal.in","r",stdin);
    //freopen("heal.out","w",stdout);
    for(int T=getint();T;T--)cout<'\n';
}

你可能感兴趣的:(期望dp)