[CSP-S模拟测试]:树(树形DP+期望)

探讨在一棵树状结构上,通过三次深度优先搜索解决梦游者从任意两点间醒来期望路径长度的问题,采用模运算避免精度误差。

题目描述

梦游中的你来到了一棵$N$个节点的树上。你一共做了$Q$个梦,每个梦需要你从点$u$走到点$v$之后才能苏醒,由于你正在梦游,所以每到一个节点后,你会在它连出去的边中等概率地选择一条走过去,为了确保第二天能够准时到校,你要求出每个梦期望经过多少条边才能苏醒。为了避免精度误差,你要输出答案模${10}^9+7$的结果。


输入格式

第一行两个整数分别代表$N$和$Q$。接下来$N-1$行,每行两个整数$u,v$代表树中的一条边。接下来$Q$行,每行两个整数代表询问的$u,v$。


输出格式

一共$Q$行,每行一个整数代表答案。


样例

样例输入:

4 2
1 2
2 3
3 4
1 4
3 4

样例输出:

9
5


数据范围与提示

对于$20\%$的数据,$N\leqslant 10$。
对于$40\%$的数据,$N\leqslant 1,000$。
另有$20\%$的数据,保证给定的树是一条链。
对于$100\%$的数据,$N\leqslant 100,000,Q\leqslant 100,000$。
如果你求出的答案为$\frac{P}{Q}$($P,Q$互质),那么你需要输出$P\times Q^{{10}^9+5}$。


题解

从$u$走到$v$一定是从$u$到$fa[u]$,在到$fa[fa[u]],...,$再从$u,v$的$lca$一步一步走到$v$。
那么如果能够算出从$u$到$fa[u]$的期望步数(设为$f[u]$)和从$fa[u]$到$u$的期望步数(设为$g[u]$)就能够做了。

那么我们可以列出如下状态转移方程:

  $f[u]=\dfrac{1}{deg[u]}+\sum \limits_{x\in child[u]}\dfrac{f[x]+f[u]+1}{deg[u]}$

  $g[u]=\dfrac{1}{deg[fa[u]]}+\dfrac{g[u]+g[fa[u]]+1}{deg[fa[u]]}+\sum \limits_{x\in child[fa[u]]xor\ x\neq u}\dfrac{g[u]+f[x]+1}{deg[fa[u]]}$

发现式子就是在枚举第一步怎么走来列方程。
化简发现$f[u]$和$g[u]$其实是整数。
先求$f$,再求$g$,然后求$lca$就可以了。

三遍$DFS$即可求出答案。

时间复杂度:$\Theta(3\times N)$。

期望得分:$100$分。

实际得分:$100$分。


代码时刻

#include<bits/stdc++.h>
using namespace std;
struct rec
{
	int nxt;
	int to;
}e[200000];
int head[100001],cnt;
int n,q;
int fa[100001][18],depth[100001];
long long f[100001],g[100001];
void add(int x,int y)
{
	e[++cnt].nxt=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}
void pre_dfs(int x)
{
	for(int i=head[x];i;i=e[i].nxt)
		if(!depth[e[i].to])
		{
			fa[e[i].to][0]=x;
			for(int j=1;j<=17;j++)
				fa[e[i].to][j]=fa[fa[e[i].to][j-1]][j-1];
			depth[e[i].to]=depth[x]+1;
			pre_dfs(e[i].to);
		}
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa[x][0])
			f[x]+=f[e[i].to]+1;
	f[x]++;
}
void pro_dfs(int x)
{
	long long res=0;
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to==fa[x][0])res+=g[x]+1;
		else res+=f[e[i].to]+1;
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa[x][0])
		{
			g[e[i].to]=res-f[e[i].to];
			pro_dfs(e[i].to);
		}
}
void wzc_dfs(int x)
{
	for(int i=head[x];i;i=e[i].nxt)
		if(e[i].to!=fa[x][0])
		{
			f[e[i].to]+=f[x];
			g[e[i].to]+=g[x];
			wzc_dfs(e[i].to);
		}
}
int LCA(int x,int y)
{
	if(depth[x]>depth[y])swap(x,y);
	for(int i=17;~i;i--)
		if(depth[fa[y][i]]>=depth[x])y=fa[y][i];
	if(x==y)return x;
	for(int i=17;~i;i--)
		if(fa[x][i]!=fa[y][i])
		{
			x=fa[x][i]; 
			y=fa[y][i];
		}
	return fa[x][0];
}
int main()
{
	scanf("%d%d",&n,&q);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	depth[1]=1;
	pre_dfs(1);
	f[1]=0;
	pro_dfs(1);
	wzc_dfs(1);
	while(q--)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		int lca=LCA(x,y);
		printf("%lld\n",(f[x]-f[lca]+g[y]-g[lca])%1000000007);
	}
	return 0;
}

rp++

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

### 三、高频知识分类整理 #### 图论 图论是NOIP提高组和CSP-S复赛中的重要组成部分,涵盖了多种经典算法数据结构。常见的图论问题包括最短路径、最小生成、拓扑排序、强连通分量等。链式前向星作为一种高效的图存储结构,被广泛应用于这些问题的实现中。例如,在网络流问题中,链式前向星能够高效地处理动态边的增删操作,从而提升算法的执行效率[^1]。 #### 动态规划 动态规划是竞赛中出现频率最高的算法之一,尤其在NOIP提高组和CSP-S复赛中,涉及多种类型的动态规划问题,如线性DP、区间DP树形DP、状压DP等。这些问题通常要求选手能够快速识别状态转移方程,并高效地实现代码。例如,在CSP-J满分题单中,动态规划被列为高频考之一,覆盖了2019-2024年的真题和模拟题[^1]。 #### 数学思维 数学思维在竞赛中占据重要地位,涉及数论、组合数学、概率论等知识。常见的问题包括质数分解、同余运算、排列组合、期望计算等。例如,在CSP-J满分题单中,数学思维被列为高频考之一,要求选手能够灵活运用数学知识解决实际问题[^1]。 #### 数据结构 数据结构是竞赛编程的核心内容之一,涵盖线段状数组、并查集、堆、哈希表等经典结构。这些结构在处理复杂问题时提供了高效的解决方案。例如,线段状数组常用于处理区间查询和更新问题,而并查集则在处理连通性问题时表现出色。这些问题在NOIP提高组和CSP-S复赛中频繁出现,要求选手具备扎实的数据结构基础。 #### 字符串处理 字符串处理是竞赛中的重要考,涉及KMP、AC自动机、后缀数组等经典算法。这些问题通常要求选手能够高效地处理字符串匹配、模式识别等任务。例如,在CSP-J满分题单中,字符串处理被列为高频考之一,覆盖了多种经典算法和应用场景。 #### 贪心算法 贪心算法在竞赛中占据重要地位,尤其在处理最优解问题时表现出色。常见的问题包括活动选择、背包问题、区间调度等。这类问题通常要求选手能够快速识别贪心策略,并正确证明其正确性。例如,在CSP-J满分题单中,贪心算法被列为高频考之一,覆盖了多种经典问题和应用场景。 #### 分治二分 分治二分是竞赛中的重要考,涉及二分查找、归并排序、快速选择等经典算法。这些问题通常要求选手能够灵活运用分治思想,解决复杂问题。例如,在CSP-J满分题单中,二分算法被列为高频考之一,覆盖了多种经典问题和应用场景[^1]。 #### 网络流 网络流是CSP-S复赛中的重要考,涉及最大流、最小割、费用流等经典问题。这些问题通常要求选手能够熟练掌握网络流算法,如Dinic算法、Edmonds-Karp算法等。例如,在CSP-S复赛中,网络流问题频繁出现,要求选手具备扎实的算法基础和编程能力。 #### 二叉图的遍历 二叉图的遍历是竞赛中的基础考,涉及深度优先遍历、广度优先遍历、前序遍历、中序遍历、后序遍历等经典问题。这些问题通常要求选手能够熟练掌握递归和非递归实现方式。例如,在CSP-J满分题单中,二叉遍历被列为高频考之一,覆盖了多种经典问题和应用场景。 #### 模拟构造 模拟构造是竞赛中的常见考,涉及模拟过程、构造方案等经典问题。这些问题通常要求选手能够快速理解题意,并高效实现代码。例如,在CSP-J满分题单中,模拟构造被列为高频考之一,覆盖了多种经典问题和应用场景。 #### 高精度运算 高精度运算是竞赛中的重要考,涉及大整数加减乘除、阶乘计算、幂运算等经典问题。这些问题通常要求选手能够熟练掌握高精度运算的基本技巧,并高效实现代码。例如,在CSP-J满分题单中,高精度运算被列为高频考之一,覆盖了多种经典问题和应用场景[^1]。 #### 位运算 位运算是竞赛中的重要考,涉及位移、按位、按位或、按位异或等经典问题。这些问题通常要求选手能够灵活运用位运算技巧,解决复杂问题。例如,在CSP-J满分题单中,位运算被列为高频考之一,覆盖了多种经典问题和应用场景。 #### 搜索 搜索是竞赛中的重要考,涉及深度优先搜索、广度优先搜索、双向搜索、启发式搜索等经典问题。这些问题通常要求选手能够熟练掌握搜索技巧,并高效实现代码。例如,在CSP-J满分题单中,搜索被列为高频考之一,覆盖了多种经典问题和应用场景。 ### 示例代码 ```cpp struct Edge { int to, next, weight; } edges[200005]; int head[100005], edge_count = 0; void add_edge(int u, int v, int w) { edges[edge_count].to = v; edges[edge_count].weight = w; edges[edge_count].next = head[u]; head[u] = edge_count++; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值