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);
}