P5405 [CTS2019]氪金手游
对于这样的树形结构,我们可以比较轻易地算出若干孩子都晚于父亲的概率,但是如果存在既需要满足先于,有需要满足后于,实际上我们就不方便计算了。我们因此考虑容斥,我们钦定一条边只有两种状态,晚于父亲或随意。
我们定义边集 \(S\) 表示被钦定的边,我们定义 \(f_{S}\) 表示所有至少在 \(S\) 中边晚于父亲的概率,\(g_{S}\) 表示恰好都是在 \(S\) 中的边,晚于父亲的概率,我们最后要求的实际上就是题目给定的边集的 \(g\) 。
需要注意的是,这里概率可以容斥的原因是在于两两集合之间的概率都是独立的。
首先我们可以易得两个式子:
\[f_S=\sum_{S\subseteq T\subseteq U}g_T\\
g_S=\sum_{S\subseteq T\subseteq U}(-1)^{|T|-|S|}f_T
\]
然后 \(f_S\) 我们是比较好求的?我们考虑一个集合的边可以构成若干个连通块,对于一个连通块我们让其最上方的一个元素在所有与其相连的部分中最先出现,然后类似于子树递归处理即可。
我们定义 \(h_{S,u}\) 表示在当前集合 \(S\) 下,子树 \(u\) 中满足集合 \(S\) 的概率,\(w_{S,u}\) 表示在 \(u\) 下方的统一连通块的概率权值之和。
\[h_{S,u}=\frac{W_u}{w_{S,u}}\prod_{v}h_{S,v}
\]
但是上面的式子是在我们概率确认的情况下搞的,这里明显是不太行的,但是发现概率权值也只有三种情况,所以我们可以直接枚举。同时再加一维表示下方的和为某一值时的概率。
\[h_{S,u,i}=\sum_{j=1}^3\frac{p_{u,j}\cdot j}{i}\prod_{e_{u,v}\in S,\sum k=i-j}h_{S,v,k}\prod_{e_{u,v}\not\in S,k}h_{S,v,k}
\]
然后我们考虑把容斥系数丢进去,发现就是每一个 \(e_{u,v}\in S\) 的位置乘上一个 \(-1\) 即可。记得我们要枚举的边是题目给我们的集合的超集。
注:树上背包枚举 \(\text{siz}\) 才能保证 \(O(n^2)\) 。
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+5;
const int MOD=998244353;
int ADD(int x,int y){return x+y>=MOD?x+y-MOD:x+y;}
int TIME(int x,int y){return (int)(1ll*x*y%MOD);}
int ksm(int x,int k=MOD-2){int res=1;for(;k;k>>=1,x=TIME(x,x))if(k&1)res=TIME(res,x);return res;}
int n,p[N][4];
struct Edge{int nxt,to;bool tag;}e[N<<1];int fir[N];
void add(int u,int v,bool w,int i){e[i]=(Edge){fir[u],v,w},fir[u]=i;}
int f[N][N*3],g[N*3],siz[N],res=0;
void dfs(int u,int fa){
for(int i=fir[u];i;i=e[i].nxt){
int v=e[i].to;if(v!=fa) dfs(v,u);
}
siz[u]=1;
for(int j=1;j<=3;++j) f[u][j]=TIME(j,p[u][j]);
for(int i=fir[u];i;i=e[i].nxt){
int v=e[i].to;if(v==fa) continue;
for(int j=1;j<=siz[u]*3;++j) g[j]=f[u][j],f[u][j]=0;
if(e[i].tag){
for(int j=1;j<=siz[u]*3;++j){
for(int k=1;k<=siz[v]*3;++k)
f[u][j+k]=ADD(f[u][j+k],TIME(g[j],f[v][k]));
}
}
else{
int sum=0;
for(int j=1;j<=siz[v]*3;++j) sum=ADD(sum,f[v][j]);
for(int j=1;j<=siz[u]*3;++j){
f[u][j]=ADD(f[u][j],TIME(g[j],sum));
for(int k=1;k<=siz[v]*3;++k)
f[u][j+k]=ADD(f[u][j+k],TIME(g[j],MOD-f[v][k]));
}
}
siz[u]+=siz[v];
}
for(int i=1;i<=siz[u]*3;++i) f[u][i]=TIME(f[u][i],ksm(i));
}
int main(){
cin>>n;
for(int i=1;i<=n;++i){
int x,y,z,inv;scanf("%d%d%d",&x,&y,&z),inv=ksm(x+y+z);
p[i][1]=TIME(x,inv),p[i][2]=TIME(y,inv),p[i][3]=TIME(z,inv);
}
for(int i=1;i<n;++i){
int u,v;scanf("%d%d",&u,&v);
add(u,v,true,i<<1),add(v,u,false,i<<1|1);
}
dfs(1,0);
for(int i=1;i<=siz[1]*3;++i) res=ADD(res,f[1][i]);
return printf("%d\n",res),0;
}
/*
我树上背包不会 $O(n^2)$ 了?他是 $O(n^2)$ 的嘛?
*/