20200501省选模拟赛 a(概率生成函数+推式子)

 

题解

好题,但是这个解法适用范围比较窄,也没有多大的用处    ____by   Freopen

我们先把p数组写成概率生成函数的形式,设

P(x)=\sum_{i=0}^kp_i*x^i

我们发现x^i的系数表示我们走一步到位置 i 的概率是多少

那么P^2(x)中x^i的系数就表示我们走2步到位置 i 的概率是多少

我们发现答案的概率生成函数(设为Q(x))就是P^n(x),设

Q(x)=\sum_{i=0}^{nk}q_i*x^i=P^n(x)

最后我们只需要保留一下前面的0~t项(这里我们替换一下变量,原题用的x与生成函数用的x重复了)

答案就是

\sum_{i=0}^ti*q_i+(1-\sum_{i=0}^tq_i)*t

所以我们有了一个暴力的做法——多项式快速幂O(n*t)

可以用NTT优化一下做到O(t*logn)

然而只有40分(话说这部分分也太少了吧)

 

接下来我们就进入开挂模式(也许这是一个数学中的常用套路吧。。)

(P^{n+1}(x))'=(n+1)P^n(x)*P'(x)(求导的链式法则)

(P^{n+1}(x))'=P(x)*(P^n(x))'+P'(x)*P^n(x)(乘法的求导)

于是我们就有了

n*P^n(x)*P'(x)=P(x)*(P^n(x)')

n*Q(x)*P'(x)=Q'(x)*P(x)

然后把它们展开

n\sum_{i=0}^{nk}q_i*x^i*\sum_{j=0}^{k-1}(j+1)p_{j+1}*x^j=\sum_{i=0}^{nk-1}(i+1)q_{i+1}*x^i*\sum_{j=0}^kp_j*x^j

由待定系数法可知,左右同次的x的系数是相等的

设s=i+j,则x^s的系数可以表示为

n\sum_{j=0}^{k-1}(j+1)*p_{j+1}*q_{s-j+1}=\sum_{j=0}^k(s-j+1)*q_{s-j+1}*p_j

我们把右边j=0的那一项单独提出来

n\sum_{j=0}^{k-1}(j+1)*p_{j+1}*q_{s-j+1}=(s+1)*q_{s+1}*p_0+\sum_{j=1}^k(s-j+1)*q_{s-j+1}*p_j

移一下项

p0*(s+1)*q_{s+1}=

n\sum_{j=0}^{k-1}(j+1)*p_{j+1}*q_{s-j+1}-\sum_{j=1}^k(s-j+1)*q_{s-j+1}*p_j

换一下求和的范围

\sum_{j=1}^{k}n*j*p_j*q_{s-j+1}-(s-j+1)*q_{s-j+1}*p_j

最终得到:

p0*(s+1)*q_{s+1}=\sum_{j=1}^{k}(n*j*-(s-j+1))*q_{s-j+1}*p_j

所以

q_{s+1}=\frac{\sum_{j=1}^k(n*j-(s-j+1))*q_{s-j+1}*p_j}{p0*(s+1)}

令s=s+1

q_s=\frac{\sum_{j=1}^k(n*j-(s-j))*q_{s-j}*p_j}{p0*s}

这样就可以O(k*t)计算q数组了

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100000005
const int mod=998244353;
int inv[N],f[N],p[105];
int ksm(int x,int y)
{
	int ret=1;
	while(y){
		if(y&1)ret=1ll*ret*x%mod;
		y>>=1;x=1ll*x*x%mod;
	}
	return ret;
}
int main()
{
	int n,k,t,i,j,s=0,ans=0;
	int ip0;
	scanf("%d%d%d",&n,&k,&t);
	for(i=0;i<=k;i++){scanf("%d",&p[i]);s+=p[i];}
	s=ksm(s,mod-2);
	for(i=0;i<=k;i++)p[i]=1ll*p[i]*s%mod;
	ip0=ksm(p[0],mod-2);
	f[0]=ksm(p[0],n);
	ans=-1ll*t*f[0]%mod;
	for(i=1;i<=t;i++){
		if(i==1)inv[i]=1;
		else inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		int sum=0;
		for(j=1;j<=k&&j<=i;j++)
			sum=(1ll*sum+1ll*(1ll*p[j]*j%mod*n%mod-1ll*p[j]*(i-j)%mod+1ll*mod)%mod*f[i-j])%mod;
		f[i]=1ll*sum*ip0%mod*inv[i]%mod;
		ans=(1ll*ans+1ll*f[i]*(i-t))%mod;
	}
	printf("%d\n",((1ll*ans+1ll*t)%mod+mod)%mod);
}

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值