牛客网刷题:游游的二进制树
题目描述
给定一棵树,共有n个节点,每个节点都有一个权值:0或者1。每条路径代表一个二进制数。求有多少条路径代表的二进制数在[l,r]区间范围内?
注意:路径长度至少为1,例如,节点3到节点3虽然有一个权值,但并不是合法路径!
题目分析
这道题的核心思想是使用DFS(深度优先搜索)遍历树中的所有合法路径,并计算每条路径对应的二进制数值。
- 对于每个节点,我们需要从它出发,尝试所有可能的路径
- 在遍历过程中,维护当前路径形成的二进制数值
- 当二进制数值在[l,r]范围内时,计数加1
- 注意剪枝:当二进制数值已经超过r时,不需要继续探索
代码实现
#include<iostream>
#include<vector>
#define int long long
using namespace std;
vector<int> a[1010]; // 邻接表存储树的结构
string s; // 存储每个节点的权值
int n, l, r; // n:节点数,[l,r]:目标区间
int res; // 结果计数
// DFS函数:u是上一个节点,v是当前节点,sum是到当前为止形成的二进制数值
void dfs(int u, int v, int sum){
for(auto t : a[v]){
if(t != u){ // 避免回到来源节点
int y = sum * 2 + s[t] - '0'; // 更新二进制数值
if(y <= r){ // 如果还在上限范围内
if(y >= l){ // 如果在目标区间内
res++; // 计数加1
}
dfs(v, t, y); // 继续DFS
}
}
}
}
signed main(){
cin >> n >> l >> r;
cin >> s;
s = " " + s; // 将索引调整为从1开始
// 建立树的结构
for(int i = 1; i <= n - 1; i++){
int u, v;
cin >> u >> v;
a[u].push_back(v); // 无向图,添加双向边
a[v].push_back(u);
}
// 从每个节点出发尝试所有路径
for(int i = 1; i <= n; i++){
dfs(i, i, s[i] - '0');
}
cout << res << "\n";
}
算法解析
1. 数据结构
- 使用邻接表
vector<int> a[1010]
存储树的结构 - 使用字符串
s
存储每个节点的权值(0或1) - 变量
res
用于统计符合条件的路径数量
2. DFS遍历
关键的DFS函数实现了路径的遍历和二进制数值的计算:
void dfs(int u, int v, int sum)
u
:上一个访问的节点(用于避免回头)v
:当前正在访问的节点sum
:当前路径形成的二进制数值
3. 二进制数值计算
在DFS过程中,每访问一个新节点,二进制数值的更新公式为:
int y = sum * 2 + s[t] - '0';
这相当于二进制数左移一位,然后加上新节点的权值。
4. 条件判断与剪枝
- 当二进制数值
y > r
时,不再继续DFS,这是一个重要的剪枝 - 当
l <= y <= r
时,结果计数加1
5. 避免重复访问
参数 u
记录了上一个访问的节点,确保不会回到刚刚访问过的节点:
if(t != u)
6. 从每个节点出发
主函数中,我们从每个节点出发,尝试所有可能的路径:
for(int i = 1; i <= n; i++){
dfs(i, i, s[i] - '0');
}
时间复杂度分析
- 时间复杂度:O(n²),其中n是节点数量。在最坏情况下,每个节点都可能与其他节点形成路径。
- 空间复杂度:O(n),用于存储树的结构和递归调用栈。
示例解析
示例1
4 4 5
1010
1 2
2 3
3 4
树的结构:1 - 2 - 3 - 4
节点权值:1, 0, 1, 0
符合条件的路径有3条:
- 1-2-3 = 101₂ = 5₁₀
- 3-2-1 = 101₂ = 5₁₀
- 4-3-2-1 = 0101₂ = 5₁₀
示例2
3 1 2
100
1 2
1 3
树的结构:
1
/ \
2 3
节点权值:1, 0, 0
所有合法路径的二进制值都在[1,2]范围内,共6条路径。
总结
这道题的关键在于:
- 理解如何在树上使用DFS遍历所有路径
- 知道如何动态计算二进制数值
- 利用剪枝提高效率
该算法能够高效地解决这个问题,通过一次DFS遍历就能计算出所有符合条件的路径数量。