传送门:牛客
题目描述:
题目较长,此处暂略
输入:
5
2 1
5 2
3 2
4 3
2 1 5
3 1 2
2 1 1
2 1 1
3 1 2
输出:
208333335
166666668
166666668
950000007
508333337
一道树形dp的题目,官方题解说这是一道简单题?然而我感觉这道题至少比同场次比赛的那道图论题要难,赛时对于这道题完全没有思路,感觉主要是概率贡献变成了思维障碍点,准备以后多写一点关于概率的题目.
考虑用
f
[
u
]
[
i
]
f[u][i]
f[u][i]来记录以
u
u
u为根的点集中有
i
i
i个点,且不包括初始感染点的概率贡献和
用
g
[
u
]
[
j
]
g[u][j]
g[u][j]来记录以
u
u
u为根的点集中有
j
j
j个点,且包括初始感染点的概率贡献和
显然当我们的
i
,
j
>
0
i,j>0
i,j>0的时候,我们的
u
u
u节点是必选的
我们用 a [ ] a[] a[]来记录一个点成为初始感染点的概率,用 p [ ] p[] p[]来记录一个点被传染的概率
那么对于一个父节点
u
u
u和
u
u
u此时的一个儿子
v
v
v,考虑使用树形背包dp,目前有三种情况:
1.父节点及之前的儿子中选了初始点,当前
v
v
v没选初始点,
2.父节点及之前的儿子没选初始点,当前
v
v
v没选初始点
3.父节点及之前的儿子没选初始点,当前
v
v
v没选初始点
那么对于当前的
u
u
u来说,此时我们先考虑维护
u
u
u节点的
f
[
]
[
]
f[][]
f[][]数组,此时意味着我们的
u
u
u节点的子集没有初始感染点,那么意味此时我们的转移显然为
f
[
u
]
[
i
+
j
]
=
f
[
u
]
[
i
]
+
f
[
v
]
[
j
]
f[u][i+j]=f[u][i]+f[v][j]
f[u][i+j]=f[u][i]+f[v][j]当然为了避免我们的数组在递推过程中产生后效性,我们可以用一个
t
e
m
p
temp
temp数组来先记录,然后再将值转移给我们的
f
f
f数组
我们再来考虑
u
u
u节点的
g
[
]
[
]
g[][]
g[][]数组,此时意味着我们的
u
u
u节点的子树(包括他自身)包含了一个初始感染点,那么对于此时我们枚举的
v
v
v来说,我们的初始感染点可能在
u
u
u的其他子树里,也有可能在
v
v
v的子树里.那么此时我们的转移方程就是
g
[
u
]
[
i
+
j
]
=
g
[
u
]
[
i
]
∗
f
[
v
]
[
j
]
+
f
[
u
]
[
i
]
∗
g
[
v
]
[
j
]
g[u][i+j]=g[u][i]*f[v][j]+f[u][i]*g[v][j]
g[u][i+j]=g[u][i]∗f[v][j]+f[u][i]∗g[v][j].当然为了避免产生后效性,我们同样需要用一个
t
e
m
p
temp
temp数组来先记录
那么至此,我们得到了 u u u节点及其子树的两种情况的概率贡献.
但是此时我们需要做的是将 u u u节点的贡献累加到我们的最终答案中.那么对于 u u u节点,我们此时只需要考虑全部感染点都在 u u u节点的贡献(因为我们有感染点不在 u u u点中时,这就意味着全部感染点被一个更大的子树包括,我们可以在之后的dfs中将这个情况累加)
对于 g [ u ] [ i ] g[u][i] g[u][i],他包括了所有感染点,这就意味着它的父亲及其其他子树没有感染点.直接计算概率似乎比较麻烦,但是实际上我们此时有一个比价巧妙的转化思想,那就是当我们的u的父节点没有被感染的概率其实就是其他所有点没有被感染的概率,因为我们的感染的所有点肯定是一个联通块,而u的父节点恰好是u与其他点相连的一个必经之路
对于分数取模,我们采用逆元即可,对于逆元的方法,此处不在赘述
下面是具体的代码部分:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
#define int long long
#define maxn 1000000
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
const int mod=1e9+7;
ll qpow(ll a,ll b) {
ll ans=1;
while(b) {
if(b&1) ans=(ans*a)%mod;
b>>=1;
a=(a*a)%mod;
}
return ans;
}
vector<int>edge[maxn];
int a[maxn],b[maxn],c[maxn];int p[maxn];int ans[maxn];
int n;
int f[2010][2010],g[2010][2010];int Size[maxn];int sum=0;
int Temp1[maxn],Temp2[maxn];
//f不包括初始感染点,g包括初始感染点
void dfs(int u,int pre_u) {
Size[u]=1;a[u]=a[u]*sum%mod;
f[u][1]=p[u];g[u][1]=a[u];
for(int i=0;i<edge[u].size();i++) {
int v=edge[u][i];
if(v==pre_u) continue;
dfs(v,u);
for(int i=1;i<=Size[u]+Size[v];i++) {
Temp1[i]=Temp2[i]=0;
}
for(int i=1;i<=Size[u];i++) {
for(int j=0;j<=Size[v];j++) {
Temp1[i+j]=(Temp1[i+j]+f[u][i]*f[v][j]%mod)%mod;
Temp2[i+j]=(Temp2[i+j]+g[u][i]*f[v][j]%mod)%mod;
if(j) Temp2[i+j]=(Temp2[i+j]+f[u][i]*g[v][j]%mod)%mod;
}
}
Size[u]+=Size[v];
for(int i=1;i<=Size[u];i++) {
f[u][i]=Temp1[i];
g[u][i]=Temp2[i];
}
}
for(int i=1;i<=Size[u];i++) {
ans[i]=(ans[i]+(1-p[pre_u]+mod)%mod*g[u][i]%mod)%mod;
}
f[u][0]=(1-p[u]+mod)%mod;
}
signed main() {
n=read();
for(int i=1;i<=n-1;i++) {
int u=read(),v=read();
edge[u].push_back(v);
edge[v].push_back(u);
}
for(int i=1;i<=n;i++) {
a[i]=read();b[i]=read();c[i]=read();
p[i]=b[i]*qpow(c[i],mod-2)%mod;
sum=(sum+a[i])%mod;
}
sum=qpow(sum,mod-2);
dfs(1,0);
for(int i=1;i<=n;i++) cout<<ans[i]<<endl;
return 0;
}