题目
n(n<=2e3)个点的树,点i有一个初始权重ai(1<=ai<=1e9,sumai<=1e9),
表示有的概率,成为这棵树初始被感染的点,初始被感染的点恰有一个,
此外,点i还有一个被相邻节点传染的概率pi(0<=pi<=1),
即若相邻节点感染且i此时未被感染,则有pi的概率会被感染,
pi实际以bi/ci的方式给出(1<=bi<=ci<=1e9,gcd(bi,ci)=1),
对于k=1,2,..,.n,分别输出最终局面恰有k个节点被感染的概率,答案对1e9+7取模
即若答案分数为p/q,则输出p*q的逆元对1e9+7取模的值
题解
dp[i][j][k]表示仅考虑i的子树,选了j个点,选/没选初始点的概率之和
特别地,j>0时,钦定i这个点必选,因为最终答案一定是一个连通块,
记a[u]为u初始感染的概率,p[u]为u后来被传染的概率,
则如果背包选了u这个点,两种状态可以选一种,dp[i][1][1]=a[u],dp[i][1][0]=p[u],
dp[i][0][1]是不合法的状态,概率为0
dp[i][0][0]=1-p[u],如果u往上的点想不传染到u这棵子树的所有点,只需要u不被传染即可
转移考虑枚举u和v子树最终感染的点的连通块大小i(i>0)、j(j>=0),
根据第三维分三种情况,
①u没选初始点,v没选初始点,0,0转移到0
②u选了初始点,v没选初始点,1,0转移到1
③u没选初始点,v选了初始点,0,1转移到1
做一个朴素的树上背包,用sz的trick控制到近似O(n^2)
统计答案时,分别地考虑每个i子树的贡献,即必选i、必不选fa[i]的贡献
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e3+10,mod=1e9+7;
int n,u,v,b,c,sum;
int dp[N][N][2],a[N],tmp[N][2];
int ans[N],p[N],sz[N];
vector<int>e[N];
int modpow(int x,int n,int mod){
int res=1;
for(;n;n>>=1,x=1ll*x*x%mod){
if(n&1)res=1ll*res*x%mod;
}
return res;
}
void add(int &x,int y){
x=(x+y)%mod;
}
void dfs(int u,int fa){
a[u]=1ll*a[u]*sum%mod;
dp[u][1][0]=p[u];
dp[u][1][1]=a[u];
sz[u]=1;
for(auto &v:e[u]){
if(v==fa)continue;
dfs(v,u);
for(int i=1;i<=sz[u]+sz[v];++i){
tmp[i][0]=tmp[i][1]=0;
}
for(int i=1;i<=sz[u];++i){
for(int j=0;j<=sz[v];++j){
add(tmp[i+j][0],1ll*dp[u][i][0]*dp[v][j][0]%mod);
add(tmp[i+j][1],1ll*dp[u][i][1]*dp[v][j][0]%mod);
if(j)add(tmp[i+j][1],1ll*dp[u][i][0]*dp[v][j][1]%mod);
}
}
for(int i=1;i<=sz[u]+sz[v];++i){
dp[u][i][0]=tmp[i][0];
dp[u][i][1]=tmp[i][1];
}
sz[u]+=sz[v];
}
for(int i=1;i<=sz[u];++i){
add(ans[i],1ll*(1-p[fa]+mod)%mod*dp[u][i][1]%mod);
}
dp[u][0][0]=(1-p[u]+mod)%mod;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;++i){
scanf("%d%d",&u,&v);
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;++i){
scanf("%d%d%d",&a[i],&b,&c);
sum=(sum+a[i])%mod;
p[i]=1ll*b*modpow(c,mod-2,mod)%mod;
}
sum=modpow(sum,mod-2,mod);
dfs(1,0);
for(int i=1;i<=n;++i){
printf("%d\n",ans[i]);
}
return 0;
}