【题目】
原题地址
nnn个人,每个敌人分别有mim_imi的生命值,接下来释放QQQ个技能。

  • 指定一个敌人ididid,有ppp的概率使得这个敌人的生命1-11
  • 指定kkk个敌人,等概率的从这kkk个人中选取一个活着的人封印,问这kkk个人中每个人被封印的概率分别是多少。
    处理完所有操作后求每个人最终血量期望,血量不会低于000
    n200,mi100,Q2×105n\\leq 200,m_i\\leq 100,Q\\leq 2\\times 10^5n200,mi100,Q2×105,二技能次数1000\\leq 10001000,答案对998244353998244353998244353取模。

【解题思路】
又是一道很妙的概率题。

首先我们不看二技能,实际上就是一个简单的DP\\text{DP}DP,我们设fi,jf_{i,j}fi,j表示第iii个人当前血量为jjj的概率,那么对于每次一技能fi,j=fi,j×(1p)+fi,j+1×pf'_{i,j}=f_{i,j}\\times (1-p)+f_{i,j+1}\\times pfi,j=fi,j×(1p)+fi,j+1×p,特别地,对于j=0j=0j=0,fi,0=fi,0+fi,1×pf'_{i,0}=f_{i,0}+f_{i,1}\\times pfi,0=fi,0+fi,1×p(即血量为000时不会继续扣血),这一部分的复杂度是O(mQ)O(mQ)O(mQ)的。

我们现在来考虑二技能,设alivexalive_xalivexdeadxdead_xdeadx,分别表示xxx在当前情况下存活或死亡的概率,那么deadx=fx,0,alivex=1deadxdead_x=f_{x,0},alive_x=1-dead_xdeadx=fx,0,alivex=1deadx。令gjg_jgj表示当前选定集合中不包括当前封印的人情况下,有jjj个人仍然存活,每次新加入一个人xxx(不包括当前封印的人iii),则gj=alivex×gj1+deadx×gjg'_j=alive_x\\times g_{j-1}+dead_x\\times g_jgj=alivex×gj1+deadx×gj

那么当前被封印的人为iii的概率即为alivei×j=0k1gjj+1alive_i\\times \\sum_{j=0}^{k-1} \\frac {g_j} {j+1}alivei×j=0k1j+1gj

这样做我们需要枚举被封印的人,再进行O(k2)O(k^2)O(k2)DP\\text{DP}DP,每个询问是O(k3)O(k^3)O(k3)的,需要考虑优化。

一个十分妙的情况在于上面的式子是可逆的,即gj=gjalivex×gj1deadxg_j=\\frac {g'_j -alive_x \\times g_{j-1}} {dead_x}gj=deadxgjalivex×gj1,于是我们可以对所有人一起求出共有的ggg数组,最后回代一步,这样每个询问就是O(k2)O(k^2)O(k2)的了。

总的时间复杂度就是O(mQ+n2C)O(mQ+n^2C)O(mQ+n2C)的了。

表示sb了,有一个快速幂的log没有写到外面去一直70pt。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=405,mod=998244353;
int n,m,Q,a[N],d[N];
ll rem[N],g[N],inv[N],f[N][N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}
void write(ll x){if(x>9)write(x/10);putchar((x%10)^48);}
ll qpow(ll x,int y){ll res=1;for(;y;y>>=1,x=x*x%mod)if(y&1)res=res*x%mod;return res;}
ll upm(ll x){x%=mod;if(x<0)x+=mod;return x;}
void up(ll &x,ll y){x+

					
				
收藏 打印
您的足迹: