P4564 [CTSC2018]假面
首先容易看出结界技能对第二问敌方剩余生命值期望没有影响。
如何求出第
i
i
i个人的剩余生命值期望?
只需要根据
E
i
=
∑
j
=
0
a
i
j
×
f
i
,
j
E_i=\sum_{j=0}^{a_i}j×f_{i,j}
Ei=∑j=0aij×fi,j
预处理
f
i
,
j
f_{i,j}
fi,j:第
i
i
i个人的剩余生命值为
j
j
j的期望(
a
i
a_i
ai表示最初生命值)
由于每次锁定技能只能明确对一名地方单位造成攻击(
p
p
p概率击中而
q
q
q概率不中),每次只需要
O
(
a
i
)
O(a_i)
O(ai)的代价维护
f
i
,
j
f_{i,j}
fi,j总的时间复杂度
O
(
Q
m
)
O(Qm)
O(Qm)
转移方程:
f
i
,
j
=
p
f
i
,
j
+
1
+
q
f
i
,
j
f_{i,j}=pf_{i,j+1}+qf_{i,j}
fi,j=pfi,j+1+qfi,j,注意
f
i
,
0
=
p
f
i
,
1
+
f
i
,
0
f_{i,0}=pf_{i,1}+f_{i,0}
fi,0=pfi,1+fi,0
对于第二个技能,想要知道命中 u u u的概率,我们需要知道除了u之外还有 j j j个人存活下,不妨叫做 g u , j g_{u,j} gu,j。
只需要把除了
u
u
u的其他敌人
v
v
v拿出来跑一遍背包即可(注意逆序)
g
u
,
j
=
alive
v
×
g
u
,
j
−
1
+
dead
v
×
g
u
,
j
g_{u,j}=\text{alive}_v×g_{u,j-1}+\text{dead}_v×g_{u,j}
gu,j=alivev×gu,j−1+deadv×gu,j
显然 alive v = 1 − f v , 0 \text{alive}_v=1-f_{v,0} alivev=1−fv,0:v存活下来的概率, dead v = f v , 0 \text{dead}_v=f_{v,0} deadv=fv,0死了的概率。
对于第二个技能范围的每个敌人,我们都需要预处理一下 g u , j g_{u,j} gu,j数组,也就是 O ( n 3 ) O(n^3) O(n3)的复杂度,第二个技能总时间复杂度 O ( C n 3 ) O(Cn^3) O(Cn3),代码如下这时候我们能够拿到 70 70 70pts, O ( Q m + C n 3 ) O(Qm+Cn^3) O(Qm+Cn3)
#include<cstring>
#include<iostream>
using namespace std;
using ll=long long;
constexpr ll mod=998244353;
ll qmi(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
ll inv[205],a[205],b[205];
ll f[205][105];//f[i][j]第i个人还有j滴血的概率
ll g[205];//将u那一维压缩了
int n,m;
void attack(int i,ll p)//O(qc)
{
ll q=(1-p+mod)%mod;
for(int j=0;j<=a[i];j++)
{
if(j)
f[i][j]=(p*f[i][j+1]%mod+q*f[i][j]%mod)%mod;
else
f[i][j]=(p*f[i][j+1]%mod+f[i][j])%mod;
}
}
void solve(int k)//O(n^3)
{
for(int u=1;u<=k;u++)//枚举范围呢的每一个敌人
{
memset(g,0,sizeof g);g[0]=1ll;
for(int i=1;i<=k;i++)//除了u的敌人跑一边背包
{
if(u==i) continue;
ll alive=((1ll-f[b[i]][0])%mod+mod)%mod;
ll dead=((1ll-alive)%mod+mod)%mod;
for(int j=i;j>=0;j--)//逆序!
{
if(j)
g[j]=(g[j]*dead+g[j-1]*alive%mod)%mod;
else
g[j]=g[j]*dead%mod;
}
}
ll ans=0;
for(int j=0;j<k;j++) ans=(ans+g[j]*inv[j+1]%mod)%mod;
ans=ans*((1ll-f[b[u]][0])%mod+mod)%mod;
cout<<ans<<' ';
}
cout<<'\n';
}
int main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
inv[i]=qmi(i,mod-2);
f[i][a[i]]=1;
}
cin>>m;
while(m--)
{
int op;
cin>>op;
if(op==0)
{
int id;ll u,v;
cin>>id>>u>>v;
attack(id,1ll*u*qmi(v,mod-2)%mod);
}
else
{
int k;
cin>>k;
for(int i=1;i<=k;i++) cin>>b[i];
solve(k);
}
}
for(int i=1;i<=n;i++)
{
ll ans=0;
for(int j=1;j<=a[i];j++)
ans=(ans+j*f[i][j]%mod)%mod;
cout<<ans<<' ';
}
return 0;
}
考虑优化,显然我们TLE是由于第二个操作,如果每次枚举然后跑背包似乎有些冗余。我们另设
g
j
g_j
gj表示
j
j
j个人或者的概率,而用
h
u
,
j
h_{u,j}
hu,j表示除了u之外还有
j
j
j个人存活下(也就是上面的
g
u
,
j
g_{u,j}
gu,j)有下面递推
g
j
=
alive
u
×
h
u
,
j
−
1
+
dead
u
×
h
u
,
j
g_j=\text{alive}_u×h_{u,j-1}+\text{dead}_u×h_{u,j}
gj=aliveu×hu,j−1+deadu×hu,j
于是有
h
u
,
j
=
g
j
−
alive
u
×
h
u
,
j
−
1
dead
u
h_{u,j}=\frac{g_j-\text{alive}_u×h_{u,j-1}}{\text{dead}_u}
hu,j=deadugj−aliveu×hu,j−1
于是我们只需要
O
(
n
2
)
O(n^2)
O(n2)预处理
g
j
g_j
gj,然后枚举
u
u
u线性求出
h
u
,
j
h_{u,j}
hu,j同样时间复杂度
O
(
n
2
)
O(n^2)
O(n2),那么技能二时间复杂度
O
(
C
n
2
)
O(Cn^2)
O(Cn2)
注意
g
j
=
alive
u
×
h
u
,
j
−
1
,
dead
u
=
0
g_j=\text{alive}_u×h_{u,j-1},\text{dead}_u=0
gj=aliveu×hu,j−1,deadu=0
即存在
h
u
,
j
=
g
j
+
1
h_{u,j}=g_{j+1}
hu,j=gj+1
总时间复杂度 O ( Q m + C n 2 ) O(Qm+Cn^2) O(Qm+Cn2)
#include<cstring>
#include<iostream>
using namespace std;
using ll=long long;
constexpr ll mod=998244353;
ll qmi(ll a,ll b)
{
ll res=1;
while(b)
{
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
ll inv[205],a[205],b[205];
ll f[205][105];
ll g[205],h[205];//h同样可以少一维
int n,m;
void attack(int i,ll p)//O(qc)
{
ll q=(1-p+mod)%mod;
for(int j=0;j<=a[i];j++)
{
if(j)
f[i][j]=(p*f[i][j+1]%mod+q*f[i][j]%mod)%mod;
else
f[i][j]=(p*f[i][j+1]%mod+f[i][j])%mod;
}
}
void solve(int k)
{
memset(g,0,sizeof g);g[0]=1ll;
for(int i=1;i<=k;i++)//预处理 g
{
ll alive=((1ll-f[b[i]][0])%mod+mod)%mod;
ll dead=((1ll-alive)%mod+mod)%mod;
for(int j=i;j>=0;j--)
{
if(j)
g[j]=(g[j]*dead+g[j-1]*alive%mod)%mod;
else
g[j]=g[j]*dead%mod;
}
}
for(int u=1;u<=k;u++)
{
ll ans=0;
ll alive=((1ll-f[b[u]][0])%mod+mod)%mod;
ll dead=((1ll-alive)%mod+mod)%mod;
memset(h,0,sizeof h);
if(alive!=1)//1-dead != 0
{
ll invd=qmi(dead,mod-2);
h[0]=g[0]*invd%mod;
for(int j=1;j<k;j++)
h[j]=(g[j]-alive*h[j-1]%mod+mod)%mod*invd%mod;
}
else//dead=1
{
for(int j=0;j<=k;j++)
h[j]=g[j+1];
}
for(int j=0;j<k;j++) ans=(ans+h[j]*inv[j+1]%mod)%mod;
ans=ans*alive%mod;
cout<<ans<<' ';
}
cout<<'\n';
}
int main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
inv[i]=qmi(i,mod-2);
f[i][a[i]]=1;
}
cin>>m;
while(m--)
{
int op;
cin>>op;
if(op==0)
{
int id;ll u,v;
cin>>id>>u>>v;
attack(id,1ll*u*qmi(v,mod-2)%mod);
}
else
{
int k;
cin>>k;
for(int i=1;i<=k;i++) cin>>b[i];
solve(k);
}
}
for(int i=1;i<=n;i++)
{
ll ans=0;
for(int j=1;j<=a[i];j++)
ans=(ans+j*f[i][j]%mod)%mod;
cout<<ans<<' ';
}
return 0;
}
要加油哦~