[CTSC2018]假面
题目大意:
有$n(n\le200)$个人,每个人初始血量为$m_i(m_i\le100)$对这些人进行$q(q\le2\times10^5)$次操作,操作包含以下两种:
1. 选择编号为$id$的人,有$p$的概率扣掉一滴血;
2. 编号为$id_1,id_2,\ldots,id_k$的$k$个人,等概率的从这$k$个人中选取一个活着的人封印,问这$k$个人中每个人被封印的概率是多少。
处理完所有操作后,求每个人最终血量的期望。
思路:
$f[i][j]$表示第$i$个人血量为$j$的概率,则对于每次的操作1,有转移方程$f'[i][j]=f[i][j]\times(1-p)+f[i][j+1]\times p$。特别地,对于$j=0$的情况,$f'[i][0]=f[i][0]+f[i][1]\times p$,即,血量为$0$时,不管怎么扣血,血量仍旧为$0$。
考虑操作$2$,用$alive(x)$和$dead(x)$分别表示$x$在当前局面下,存活或死亡的概率。用$g[j]$表示当前集合中不包括当前封印的有$j$人存活的概率,每次把新人$x$加入到集合中时,转移方程为$g'[j]=alive(x)\times g[j-1]+dead(x)\times g[j]$。若封印的人为$i$,则答案为$alive(i)\times\sum_{j=0}^{k-1}\frac{g[j]}{j+1}$。每次$O(k)$枚举封印的人,剩下$O(k^2)$DP,则对于每次操作2,时间复杂度为$O(k^3)$,有70分。
发现上述状态转移可逆,即$g[j]=\frac{g'[j]-alive(x)\times g[j-1]}{dead(x)}$,因此可以不考虑封印的人求出$g$数组,再枚举封印的人进行逆转移,单次操作时间复杂度$O(k^2)$。
对于期望血量,直接利用$f$数组求得。
此外本题有些卡常,需要开O2。
1 #pragma GCC optimize("Ofast") 2 #include<cstdio> 3 #include<cctype> 4 #include<algorithm> 5 typedef long long int64; 6 inline int getint() { 7 register char ch; 8 while(!isdigit(ch=getchar())); 9 register int x=ch^'0'; 10 while(isdigit(ch=getchar())) x=(((x<<2)+x)<<1)+(ch^'0'); 11 return x; 12 } 13 const int N=201,M=101,mod=998244353; 14 int m[N],f[N][M],g[N],h[N],id[N],map[N]; 15 void exgcd(const int &a,const int &b,int &x,int &y) { 16 if(!b) { 17 x=1,y=0; 18 return; 19 } 20 exgcd(b,a%b,y,x); 21 y-=a/b*x; 22 } 23 inline int inv(const int &x) { 24 if(x<N&&map[x]) return map[x]; 25 int ret,tmp; 26 exgcd(x,mod,ret,tmp); 27 const int ans=(ret%mod+mod)%mod; 28 if(x<N) map[x]=ans; 29 return ans; 30 } 31 inline int alive(const int &x) { 32 return mod-f[x][0]+1; 33 } 34 inline int dead(const int &x) { 35 return f[x][0]; 36 } 37 int main() { 38 const int n=getint(); 39 for(register int i=1;i<=n;i++) { 40 f[i][m[i]=getint()]=1; 41 } 42 const int q=getint(); 43 for(register int i=0;i<q;i++) { 44 const int opt=getint(); 45 if(opt==0) { 46 const int id=getint(),u=getint(),v=getint(),p=(int64)u*inv(v)%mod; 47 (f[id][0]+=(int64)f[id][1]*p%mod)%=mod; 48 for(register int i=1;i<=m[id];i++) { 49 f[id][i]=(int64)f[id][i]*(mod-p+1)%mod; 50 if(i!=m[id]) (f[id][i]+=(int64)f[id][i+1]*p%mod)%=mod; 51 } 52 } 53 if(opt==1) { 54 const int k=getint(); 55 std::fill(&g[g[0]=1],&g[k]+1,0); 56 for(register int i=1;i<=k;i++) { 57 id[i]=getint(); 58 for(register int j=i;~j;j--) { 59 g[j]=(int64)dead(id[i])*g[j]%mod; 60 if(j>0) (g[j]+=(int64)alive(id[i])*g[j-1]%mod)%=mod; 61 } 62 } 63 for(register int i=1;i<=k;i++) { 64 int ans=0; 65 if(!dead(id[i])) { 66 for(register int j=0;j<k;j++) { 67 (ans+=(int64)g[j+1]*inv(j+1)%mod)%=mod; 68 } 69 } else { 70 std::fill(&h[h[0]=1],&h[k],0); 71 for(register int j=1;j<=k;j++) { 72 if(i!=j) h[0]=(int64)h[0]*dead(id[j])%mod; 73 } 74 for(register int j=0;j<k;j++) { 75 if(j>0) h[j]=(int64)((g[j]-(int64)alive(id[i])*h[j-1]%mod+mod)%mod)*inv(dead(id[i]))%mod; 76 (ans+=(int64)h[j]*inv(j+1)%mod)%=mod; 77 } 78 } 79 ans=(int64)ans*alive(id[i])%mod; 80 printf("%d%c",ans," \n"[i==k]); 81 } 82 } 83 } 84 for(register int i=1;i<=n;i++) { 85 int ans=0; 86 for(register int j=1;j<=m[i];j++) { 87 (ans+=(int64)j*f[i][j]%mod)%=mod; 88 } 89 printf("%d%c",ans," \n"[i==n]); 90 } 91 return 0; 92 }