2021陕西省赛 D Disease(树状DP、期望)

该博客讨论了一道涉及概率计算和递归算法的编程题目。博主详细解析了如何通过分层和递归深度优先搜索(DFS)来计算网络中节点被感染的概率。题目中考虑了初始感染概率以及节点间相互传染的概率,并且需要计算任意层级节点被感染的总概率。博主给出了完整的C++代码实现,包括关键的递推公式和计算过程,强调了计算逻辑中哪些部分应该在DFS内部完成,哪些应该在外部完成。最后,博主提供了完整的解决方案和运行结果。
摘要由CSDN通过智能技术生成

题面:https://ac.nowcoder.com/acm/contest/35232/D

解题思路:

先分层,然后每层的深度去乘到这层的概率P。

听上去挺简单的,只要算出概率P就行.

这个概率P就是这一层以上的所有点都没被感染的概率,乘以这一层至少有一个被感染概率。

但是这题狗就狗在,不仅有初始的感染概率,还有之后点与点之间的感染概率。(注意这里向下传染是不会改变等级的,所以研究向上传染的问题就行)

所以这个概率P细分:

  • 这一层以上初始时所有点没被感染的概率—从第一层开始递推,直接累乘(1-pi)就行。
  • 这一层至少有一个被感染的概率(包括初始的和后来被感染的),这里要对每一个点进行计算,算出他被感染的概率ppi,那么这一层被感染的概率就是1-(1-pp1)(1-pp2)……
    • 对于ppi,它有两个部分,一个部分是初始就被感染,还有一个部分是初始没被感染但是儿子被感染且传给它了(这里是加号)。注意这里儿子被感染,是包括初始和之后被感染的,所以很显然这部分也要用递推,从最底层往上递推。
  • 你以为这就完了?其实仔细看第一个是初始时没被感染,还有一个是不被这一层感染的点所感染的概率啊,这个需要再计算完第二步之后,对每个点再算。

这几个递推哪个可以写在dfs里,哪个写在外面,主要就是看是按点算还是按层算。

#include<bits/stdc++.h>
#define DEBUG(x) cout<<"** "<<x<<" **"<<endl;
using namespace std;
typedef long long ll;
#define N 200005
const ll mod=1e9+7;
struct node{
	int num;
	ll p;
	node(int x,ll y){
		num=x;
		p=y;
	}
};
ll fpm(ll x){
	x%=mod;
	ll res=1;
	ll mm=mod-2;
	while(mm){
		if(mm&1)res*=x,res%=mod;
		x*=x;
		x%=mod;
		mm>>=1;
	}
	return res;
}
ll dp[N],init[N],pre[N],tofa[N];
//总的被感染的概率;初始;这一层之前全不被感染;传染给父亲的概率。
int dep[N],fa[N];
vector<node> g[N];
vector<int> la[N];//每一层的深度
int mx=0;
ll ans=0;
void dfs(int root,int f){
	dep[root]=dep[f]+1;
	fa[root]=f;
	mx=max(dep[root],mx);
	la[dep[root]].push_back(root);
	if(g[root].size()==1&&root!=1){
		dp[root]=init[root];
		return ;
	}
	ll tmp=1;
	for(int i=0;i<g[root].size();i++){
		int s=g[root][i].num;
		int t=g[root][i].p;
		if(s==f)continue;
		tofa[s]=t;
		dfs(s,root);
		tmp=(tmp*(1+mod-dp[s]*t%mod))%mod;
	}
	dp[root]=((1+mod-tmp)*(1+mod-init[root])%mod+init[root])%mod;
}
void add(int i){
	ll thistmp=1;
	ll uptmp=1;
	for(int j=0;j<la[i].size();j++){
		int s=la[i][j];
		thistmp=(thistmp*(1-dp[s]+mod))%mod;
		uptmp=(uptmp*(1+mod-dp[s]*tofa[s]%mod))%mod;
	}
	ans=(ans+pre[i-1]*(1-thistmp-(1-uptmp)+mod)%mod*i)%mod;
}
int main(){
	int n;
	cin>>n;
	ll x,y;
	for(int i=1;i<=n;i++){
		scanf("%lld %lld",&x,&y);
		y=fpm(y);
		init[i]=(x*y)%mod;
	}
	int px,py;
	for(int i=1;i<n;i++){
		scanf("%d %d %lld %lld",&px,&py,&x,&y);
		y=fpm(y);
		x=(x*y)%mod;
		g[px].push_back(node(py,x)); 
		g[py].push_back(node(px,x)); 
	}
	
	if(n==1){
		cout<<init[1]<<endl;
		return 0;
	}
	dep[0]=0;
	dfs(1,0);
	pre[0]=1;
	for(int i=1;i<=mx;i++){
		pre[i]=pre[i-1];
		for(int j=0;j<la[i].size();j++){
			int s=la[i][j];
			pre[i]=(pre[i]*(1+mod-init[s]))%mod;
		}
	}
//	for(int i=1;i<=n;i++){
//		DEBUG(pre[i]);
//	}
	for(int i=mx;i;i--){
		add(i);
	}
	cout<<ans<<endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值