[CSP-S模拟测试]:tree(DP)

题目传送门(内部题57)


输入格式

第一行包含一个数:$n$表示树的节点数。
接下来$n-1$行,每行包含两个数:$u,v$表示无根树的一条边。


输出格式

输出$n$行,第$i$行包含一个浮点数,保留三位小数,表示第$i$号点第一次访问的期望时间。


样例

样例输入:

3
1 2
2 3

样例输出:

1.000
2.000
5.000


数据范围与提示

样例解释:

样例解释:容易分析出,所有可能情况下,到达$1$号点和$2$号点的时间都分别是:$1$和$2$,我们考虑$3$号点的到达时间,所有可能的过程:$12(12)*3$,表示先到$1$号店,再到$2$号点,然后重复任意次$1$、$2$(可以是$0$次),最后到达$3$。
对于$12(12)^i3$这个具体过程来说(表示中间经过$i$次$1$、$2$),到达$3$号点的时间是$t_i=2(i+1)+1$,这个随机过程的概率是$p_i={(\frac{1}{2})}^{(i+1)}$,期望的时间是$E(u=3)=\sum \limits_{i=0}^{\infty}t_ip_i=5$,故到达$3$号点的期望时刻为$5$。

数据范围:

对于$10\%$的数据,$1\leqslant 10$,保证每个点的度不超过$2$;
对于另外$20\%$的数据,$1\leqslant n\leqslant {10}^5$,保证每个点的度不超过$2$;
对于另外$20\%$的数据,$1\leqslant n\leqslant 100$;
对于$100\%$的数据,$1\leqslant n\leqslant {10}^5$。


题解

考虑$DP$,设$dp[i]$表示到达$i$点的期望时间。

你可能会很容易的推出来一个式子:

$$dp[u]=dp[fa]+2(n-size[u])-1$$

然后你会发现没有小数,删掉它交暴力。

这就是我的考试全过程……

然而,三位小数就是逗我玩的……

无语……

下面讲一下推导:

考虑一个随机过程,第一次走到$u$号点的时间可以分成两部分,第一部分是从$1$号点随机游走第一次走到$u$的父亲$p$的时间,第二部分是从$p$开始走,第一次走到$u$的时间,由期望的线性性,第一次走到$u$的时间期望等于这两部分期望的和。第一部分是一个子问题,我们考虑怎么解决第二部分,我们把这个问题变成一棵树(并且根节点脑袋上也有一条边),从根节点开始随机游走,走出这棵树期望的时间,我们用$x_u$表示这个期望,我们对$u$的子树中的点也类似地定义$x_v$,这样我们可以列出关系式:

$$x_u=\frac{(1+\sum \limits_{v}(x_u+x_v+1))}{d}$$

其中$d$是$u$的度数(包括那根天线),这个关系是中的第一个$1$表示直接向上走,后面那个扩后中的三部分,那个$1$表示从$u$走向$v$,$x_v$表示从$v$走回来期望时间, 表示这个时候继续走,走出去还需要花的时间。因为是等概率,所以直接乘以$frac{1}{d}$这个概率即可。化简后是:

$$x_u=d+\sum \limits_{v}x_v$$

即$x_u$等于$u$这棵子树的所有节点度的和,考虑到除了那根天线之外,所有的边对度的贡献为$2$,所以:

$$x_u=2size[u]+1$$

这样,子问题就有了一个简单的答案了。我们回到原问题,用$dp[u]$表示第一次走到$u$的期望时间,用$fa$表示$u$的父亲,有:

$$dp[u]=dp[fa]+2(n-size[u])-1$$

时间复杂度:$\Theta(n)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
struct rec{int nxt,to;}e[200001];
int head[100001],cnt;
int n;
bool vis[100001];
int size[100001];
long long dp[100001];
void add(int x,int y)
{
	e[++cnt].nxt=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
void dfs1(int x)
{
	vis[x]=1;
	size[x]=1;
	for(int i=head[x];i;i=e[i].nxt)
		if(!vis[e[i].to])
		{
			dfs1(e[i].to);
			size[x]+=size[e[i].to];
		}
}
void dfs2(int x)
{
	vis[x]=1;
	for(int i=head[x];i;i=e[i].nxt)
		if(!vis[e[i].to])
		{
			dp[e[i].to]=dp[x]+2*(n-size[e[i].to])-1;
			dfs2(e[i].to);
		}
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs1(1);
	memset(vis,0,sizeof(vis));
	dp[1]=1;
	dfs2(1);
	for(int i=1;i<=n;i++)
		printf("%.3lf\n",(double)dp[i]);
	return 0;
}

rp++

转载于:https://www.cnblogs.com/wzc521/p/11586404.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值