[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\%$的数据,$1leqslant 10$,保证每个点的度不超过$2$;
对于另外$20\%$的数据,$1leqslant nleqslant {10}^5$,保证每个点的度不超过$2$;
对于另外$20\%$的数据,$1leqslant nleqslant 100$;
对于$100\%$的数据,$1leqslant nleqslant {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
",(double)dp[i]);
	return 0;
}

rp++