P4564 [CTSC2018]假面
首先容易看出结界技能对第二问敌方剩余生命值期望没有影响。
如何求出第iii个人的剩余生命值期望?
只需要根据Ei=∑j=0aij×fi,jE_i=\sum_{j=0}^{a_i}j×f_{i,j}Ei=∑j=0aij×fi,j
预处理fi,jf_{i,j}fi,j:第iii个人的剩余生命值为jjj的期望(aia_iai表示最初生命值)
由于每次锁定技能只能明确对一名地方单位造成攻击(ppp概率击中而qqq概率不中),每次只需要O(ai)O(a_i)O(ai)的代价维护fi,jf_{i,j}fi,j总的时间复杂度O(Qm)O(Qm)O(Qm)
转移方程:fi,j=pfi,j+1+qfi,jf_{i,j}=pf_{i,j+1}+qf_{i,j}fi,j=pfi,j+1+qfi,j,注意fi,0=pfi,1+fi,0f_{i,0}=pf_{i,1}+f_{i,0}fi,0=pfi,1+fi,0
对于第二个技能,想要知道命中uuu的概率,我们需要知道除了u之外还有jjj个人存活下,不妨叫做gu,jg_{u,j}gu,j。
只需要把除了uuu的其他敌人vvv拿出来跑一遍背包即可(注意逆序)
gu,j=alivev×gu,j−1+deadv×gu,jg_{u,j}=\text{alive}_v×g_{u,j-1}+\text{dead}_v×g_{u,j}gu,j=alivev×gu,j−1+deadv×gu,j
显然alivev=1−fv,0\text{alive}_v=1-f_{v,0}alivev=1−fv,0:v存活下来的概率,deadv=fv,0\text{dead}_v=f_{v,0}deadv=fv,0死了的概率。
对于第二个技能范围的每个敌人,我们都需要预处理一下gu,jg_{u,j}gu,j数组,也就是O(n3)O(n^3)O(n3)的复杂度,第二个技能总时间复杂度O(Cn3)O(Cn^3)O(Cn3),代码如下这时候我们能够拿到707070pts,O(Qm+Cn3)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;elsef[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是由于第二个操作,如果每次枚举然后跑背包似乎有些冗余。我们另设gjg_jgj表示jjj个人或者的概率,而用hu,jh_{u,j}hu,j表示除了u之外还有jjj个人存活下(也就是上面的gu,jg_{u,j}gu,j)有下面递推
gj=aliveu×hu,j−1+deadu×hu,jg_j=\text{alive}_u×h_{u,j-1}+\text{dead}_u×h_{u,j}gj=aliveu×hu,j−1+deadu×hu,j
于是有hu,j=gj−aliveu×hu,j−1deaduh_{u,j}=\frac{g_j-\text{alive}_u×h_{u,j-1}}{\text{dead}_u}hu,j=deadugj−aliveu×hu,j−1
于是我们只需要O(n2)O(n^2)O(n2)预处理gjg_jgj,然后枚举uuu线性求出hu,jh_{u,j}hu,j同样时间复杂度O(n2)O(n^2)O(n2),那么技能二时间复杂度O(Cn2)O(Cn^2)O(Cn2)
注意gj=aliveu×hu,j−1,deadu=0g_j=\text{alive}_u×h_{u,j-1},\text{dead}_u=0gj=aliveu×hu,j−1,deadu=0
即存在hu,j=gj+1h_{u,j}=g_{j+1}hu,j=gj+1
总时间复杂度O(Qm+Cn2)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;elsef[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;
}
要加油哦~