【洛谷4564】[CTSC2018] 假面(有意思的DP题)
- 有\(n\)个敌方单位,各有\(a_i\)点体力。
- 有\(q\)次操作,分为两种:
- 锁定:以\(p\)的概率对指定敌方单位造成\(1\)点伤害。
- 结界:在给定的若干敌方单位中随机选中一个存活的单位,求每个敌方单位被选中的概率。
- 最后你还要输出每个敌方单位剩余体力值的期望。
- \(n\le200,a_i\le100,q\le2\times10^5\),其中结界操作次数\(\le10^3\)
锁定操作
显然,我们可以用\(f_{i,j}\)表示\(i\)剩余\(j\)点体力值的概率。
对于一次锁定操作,假设操作对象为\(x\),概率为\(y\),显然有转移方程:
\[f_{x,i}=y\times f'_{x,i+1}+(1-y)\times f'_{x,i}
\]
但这里要注意,若\(i=0\),则后一项的转移系数为\(1\),因为当体力为\(0\)时不可能再失去体力。
结界操作
这才是这道题的核心。
我们设\(g_i\)为\(i\)个人存活的概率,那么枚举每个人,有个很显然的转移:
\[g_i=f_{w,0}\times g'_i+ (1-f_{w,0})\times g'_{i-1}
\]
好,现在我们知道了\(g_i\),问题在于怎么求出每个人的答案。
原本\(DP\)时我们考虑了每个人存活与不存活两种情况,但如果一个人能被结界选中,必须满足他存活。
所以我们要设法从\(g_i\)中消去一个人的贡献。
其实就是把我们上面的\(DP\)数组移项:
\[g_i'=\frac{g_i-(1-f_{w,i})\times g'_{i-1}}{f_{w,0}}
\]
然后强制这个人存活计算概率即可。
但要注意这里\(f_{w,0}\)作为了除数,如果\(f_{w,0}=0\)怎么办?
发现此时这个人必然存活,不需消去贡献,直接计算答案即可。
代码:\(O(qm+cn^2)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200
#define M 100
#define C 1000
#define X 998244353
using namespace std;
int n,a[N+5],w[N+5],f[N+5][M+5],g[N+5],s[N+5],Inv[N+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
main()
{
RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),f[i][a[i]]=1,Inv[i]=QP(i,X-2);
RI Qt,op,x,y,z,t;scanf("%d",&Qt);W(Qt--)
{
if(scanf("%d%d",&op,&x),!op)//锁定操作
{
scanf("%d%d",&y,&z),y=1LL*y*QP(z,X-2)%X;//分数取模
for(i=0;i<=a[x];++i) f[x][i]=(1LL*y*f[x][i+1]+1LL*(i?X+1-y:1)*f[x][i])%X;//转移
}
else//结界操作
{
for(memset(g,0,sizeof(g)),g[0]=y=1;y<=x;++y) for(scanf("%d",w+y),//求出整个DP数组
i=y;~i;--i) g[i]=(1LL*f[w[y]][0]*g[i]+(i?1LL*(X+1-f[w[y]][0])*g[i-1]:0))%X;
for(y=1;y<=x;printf("%d%c",t," \n"[y==x]),++y)//枚举每个人计算答案
if(f[w[y]][0]) for(z=QP(f[w[y]][0],X-2),t=i=0;i<=x;++i)//消去贡献
s[i]=1LL*z*(g[i]-(i?1LL*(X+1-f[w[y]][0])*s[i-1]%X:0)+X)%X,
t=(1LL*(X+1-f[w[y]][0])*s[i]%X*Inv[i+1]+t)%X;//统计答案
else for(t=0,i=1;i<=x;++i) t=(1LL*g[i]*Inv[i]+t)%X;//必然存活,直接计算
}
}
for(x=1;x<=n;printf("%d%c",t," \n"[x==n]),++x)
for(t=0,i=1;i<=a[x];++i) t=(1LL*i*f[x][i]+t)%X;return 0;//期望=∑值×概率
}
待到再迷茫时回头望,所有脚印会发出光芒