[CTS2019]氪金手游
Posted wwlw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CTS2019]氪金手游相关的知识,希望对你有一定的参考价值。
Link
Solution
先考虑外向树的情况,这样根一定是最小的,所有子节点都必须比它后访问,而所有非子节点多久抽到无所谓。假定每个点的 \\(w_i\\) 是定值,\\(s_i\\) 是 \\(i\\) 的子树和,\\(S\\) 是总和,那么概率就是
\\[\\prod_{i=1}^n \\frac{w_i}{S} \\sum_{j=0}^{\\infty} (\\frac{S-s_i}{S})^{j}=\\prod_{i=1}^n \\frac{w_i}{s_i}
\\]
发先最后答案只和每个点的 \\(w\\) 和 \\(s\\) 有关。考虑把 \\(w\\) 和 \\(s\\) 压入状态进行 dp,然后发现 \\(w\\) 可以去掉。记 \\(dp[u][x]\\) 表示考虑 \\(u\\) 这个子树, \\(w_i\\) 和为 \\(x\\) 且符合关系的概率。这样就得到一个 \\(O(n^2)\\) 的 dp。
再考虑加入反向边,一个容斥小技巧,将反向边拆成不考虑这条边(分成两个连通块),减去这条边是正向边的情况。后者就是外向树的情况,前者将其分成两个连通块,容易发现这两个连通块互不干扰,所以概率就是两个分别 dp 后的乘积,也即一个外向树森林。那么最后的概率就可以通过枚举每条反向边的方向,然后 dp,再乘上一个容斥系数,求和。复杂度 \\(O(2^n n^2)\\)
又发现我们并不在意每条边具体是什么方向,容斥系数只和反向边没有反向的数量有关,而 dp 转移只和 \\(w\\) 和 \\(s\\) 有关。所以考虑转移的时候如果是反向边就将概率乘上负一,由分配率知这样是对的。
#include<stdio.h>
const int N=1e3+7;
const int M=3e6+7;
const int Mod=998244353;
struct E{
int next,to;
bool tag;
}e[N<<1];
int head[N],cnt=0,n;
int inv[M],sz[N],dp[N][N*3],tmp[N*3];
inline void add(int id,int to){
e[++cnt]=(E){head[id],to,0};
head[id]=cnt;
e[++cnt]=(E){head[to],id,1};
head[to]=cnt;
}
void dfs(int u,int fa){
sz[u]=1;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(fa==v) continue;
dfs(v,u);
for(int x=1;x<=sz[u]*3;x++)
for(int y=1;y<=sz[v]*3;y++){
int t=1ll*dp[u][x]*dp[v][y]%Mod;
if(e[i].tag) tmp[x+y]=(tmp[x+y]-t+Mod)%Mod,tmp[x]=(tmp[x]+t)%Mod;
else tmp[x+y]=(tmp[x+y]+t)%Mod;
}
sz[u]+=sz[v];
for(int x=1;x<=sz[u]*3;x++) dp[u][x]=tmp[x],tmp[x]=0;
}
for(int i=1;i<=sz[u]*3;i++) dp[u][i]=1ll*dp[u][i]*inv[i]%Mod;
}
int main(){
scanf("%d",&n); inv[1]=1;
for(int i=2;i<M;i++)
inv[i]=1ll*(Mod-Mod/i)*inv[Mod%i]%Mod;
for(int i=1,a,b,c;i<=n;i++){
scanf("%d%d%d",&a,&b,&c);
int w=inv[a+b+c];
dp[i][1]=1ll*a*w%Mod;
dp[i][2]=1ll*b*w%Mod*2ll%Mod;
dp[i][3]=1ll*c*w%Mod*3ll%Mod;
}
for(int i=1,u,v;i<n;i++)
scanf("%d%d",&u,&v),add(u,v);
dfs(1,0); int ans=0;
for(int i=1;i<=3*n;i++) ans=(ans+dp[1][i])%Mod;
printf("%d",ans);
}
以上是关于[CTS2019]氪金手游的主要内容,如果未能解决你的问题,请参考以下文章