[CTSC2018]假面

时光怂恿深爱的人放手 提交于 2021-02-14 11:39:23

题目大意:
  有$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 }

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!