[期望DP]「BJOI2018」治疗之雨

解题思路

(g[i])表示(k)次攻击后掉(i)滴血的概率,显然有(g[i]=C_{k}^{i}(frac{1}{m+1})^i(frac{m}{m+1})^{k-i})

(f[i])表示英雄初始血量为(i)的期望存活轮数,那么显然有:

[f[i]=sum_{j=0}^{min(i+1,k)}(frac{1}{m+1})g[j]f[i+1-j]+sum_{j=0}^{j=min(i,k)}(frac{m}{m+1})g[j]f[i-j]+1 ]

血量为(n)时不能加血,所以转移有所不同:

[f[n]=sum_{j=0}^{j=min(n,k)}g[j]f[n-j]+1 ]

通过上面的第一个式子可以将(f[i])推到(f[i+1]),然后发现一个很大的问题(f[1])并不确定

所以我们将(f[1])设为(X),那么(f)的每个状态都类似(aX+b)的形式,然后跟第二个式子解出(X)的值即可.

#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
const int maxn=1505,tt=1e9+7;
struct jz{
	int x,y;
	jz(int x=0,int y=0):x(x),y(y){}
	jz operator+(const jz &b)const{return jz((x+b.x)%tt,(y+b.y)%tt);}
	jz operator-(const jz &b)const{return jz((x-b.x+tt)%tt,(y-b.y+tt)%tt);}
	jz operator*(int w)const{return jz((LL)x*w%tt,(LL)y*w%tt);}
}f[maxn],ans;
int g[maxn],n,p,m,k,T;
int qsm(int w,int b){int num=1;while(b){if (b&1) num=(LL)num*w%tt;w=(LL)w*w%tt;b>>=1;}return num;}
int work1(){
    if(k==1&&n!=1) return -1;
    g[0]=0;for(int i=1;i<n;i++) g[i]=g[max(i+1-k,0)]+1;
    g[n]=g[max(n-k,0)]+1;
    return g[p];
}
int work(){
	scanf("%d%d%d%d",&n,&p,&m,&k);int Invm=qsm(m+1,tt-2);
	if (!k) return -1;if (!m) return work1();
	int C=1;for (int i=0;i<=min(k,n);i++) g[i]=(LL)C*qsm(Invm,i)%tt*qsm((LL)m*Invm%tt,k-i)%tt,C=(LL)C*(k-i)%tt*qsm(i+1,tt-2)%tt;
	if (g[0]==1||g[0]==0) return -1;
	f[0]=jz(0,0);f[1]=jz(1,0);
	for (int i=1;i<n;i++){
		f[i+1]=(f[i]-jz(0,1))*(m+1);
		for (int j=0;j<=min(i,k);j++) f[i+1]=f[i+1]-(f[i-j]*g[j])*m;
		for (int j=1;j<=min(i+1,k);j++) f[i+1]=f[i+1]-(f[i+1-j]*g[j]);
		f[i+1]=f[i+1]*qsm(g[0],tt-2); 
	}
	ans=jz(0,1);for (int i=1;i<=min(n,k);i++) ans=ans+f[n-i]*g[i];
	ans=ans*qsm(1-g[0]+tt,tt-2);ans=ans-f[n];
	if (!ans.x&&f[p].x) return -1;int X=(LL)ans.y*qsm(tt-ans.x,tt-2)%tt;
	return ((LL)f[p].x*X+f[p].y)%tt;
}
int main(){
	freopen("exam.in","r",stdin);
	freopen("exam.out","w",stdout);
	scanf("%d",&T);while(T--) printf("%d
",work());return 0;
} 
原文地址:https://www.cnblogs.com/CHNJZ/p/10560457.html