赛后补题:2022 CCPC Guangzhou I. Infection 树形背包dp+概率贡献

传送门:牛客

题目描述:

题目较长,此处暂略
输入:
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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值