2022 China Collegiate Programming Contest (CCPC) Guangzhou Onsite I. Infection(树形dp)

题目

n(n<=2e3)个点的树,点i有一个初始权重ai(1<=ai<=1e9,sumai<=1e9),

表示有gif.latex?%5Cfrac%7Ba_%7Bi%7D%7D%7B%5Csum_%7Bi%3D1%7D%5E%7Bn%7Da_%7Bi%7D%7D的概率,成为这棵树初始被感染的点,初始被感染的点恰有一个,

此外,点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;
}

 

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值