【洛谷4564】[CTSC2018] 假面(有意思的DP题)

点此看题面

  • (n)个敌方单位,各有(a_i)点体力。
  • (q)次操作,分为两种:
    • 锁定:以(p)的概率对指定敌方单位造成(1)点伤害。
    • 结界:在给定的若干敌方单位中随机选中一个存活的单位,求每个敌方单位被选中的概率。
  • 最后你还要输出每个敌方单位剩余体力值的期望。
  • (nle200,a_ile100,qle2 imes10^5),其中结界操作次数(le10^3)

锁定操作

显然,我们可以用(f_{i,j})表示(i)剩余(j)点体力值的概率。

对于一次锁定操作,假设操作对象为(x),概率为(y),显然有转移方程:

[f_{x,i}=y imes f'_{x,i+1}+(1-y) imes f'_{x,i} ]

但这里要注意,若(i=0),则后一项的转移系数为(1),因为当体力为(0)时不可能再失去体力。

结界操作

这才是这道题的核心。

我们设(g_i)(i)个人存活的概率,那么枚举每个人,有个很显然的转移:

[g_i=f_{w,0} imes g'_i+ (1-f_{w,0}) imes g'_{i-1} ]

好,现在我们知道了(g_i),问题在于怎么求出每个人的答案。

原本(DP)时我们考虑了每个人存活与不存活两种情况,但如果一个人能被结界选中,必须满足他存活。

所以我们要设法从(g_i)中消去一个人的贡献。

其实就是把我们上面的(DP)数组移项:

[g_i'=frac{g_i-(1-f_{w,i}) imes 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," 
"[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," 
"[x==n]),++x)
		for(t=0,i=1;i<=a[x];++i) t=(1LL*i*f[x][i]+t)%X;return 0;//期望=∑值×概率
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu4564.html