写题遇到的一些树上操作

《在树上找到每一个节点的各个子树的节点个数》

实现的时间复杂度O(n),n为节点个数

原题链接:https://codeforces.com/gym/103736/problem/D

以这张图为例:在3节点上其子树的节点个数分别为1 1 2;

 

 算法实现:

首先设计一个dfs函数:int dfs(int x,int f)

其含义为:找到以x为根时,而且x的父节点为f时,其全部子树的节点个数(包含自己)

 1 // cNum[i]:记录以i为根其各个子树的大小
 2 vector<int> cNum[N];
 3 // sum[i]:记录以1为根,在i为根的时候,其子树+自身的节点个数
 4 int sum[N];
 5 void dfs(int s, int fa)
 6 {
 7     sum[s]++;
 8     for (int i = 0; i < sides[s].size(); i++)
 9     {
10         int child = sides[s][i];
11         if (child == fa)
12             continue;
13         dfs(child, s);
14         cNum[s].push_back(sum[child]);
15         sum[s] += sum[child];
16     }
17     if (n - sum[s] != 0)
18         cNum[s].push_back(n - sum[s]);
19 }

算法关键:

你肯定会想:以这种递归的方式调用下去,不是只会找到其下面的子树节点个数,其以前的父节点存在的子树不会进行查找

但是如果我们找到了全部的下面的子树节点个数,然后用n-全部的下面的子树节点个数-1

就为以前的父节点存在的子树的节点个数,即为如下这一步:

 

 

《树上的Dijkstra思想》

原题链接:https://codeforces.com/gym/401803/problem/B

 简单翻译一下为:

特雷兰是一个由n个城市和n个−1条双向道路。正如你所想象的那样,特里兰是一棵树,这意味着每对城市之间只有一条简单的道路。

Treeland总统计划在n天内在该国建立一个5G网络。每天,将根据以下规则在不同的城市建造5G天线塔:

1 .每天,必须在距离已建成天线的城市不超过k的城市建造天线。此限制不适用于第一天。

2. 如果在第i天有多个有效城市可以建造天线,则必须选择数量最少的城市。

即按照最小字典输出建造城市顺序的编号

 

 

 很显然,为了字典序最小,编号为1的城市为第一个建造的城市

然后以1城市为最新点,去更新能够到的城市,然后从其中找到全部能够到的城市的编号最小的城市

算法实现:

首先,用优先队列pq,将能够到的城市装进pq中,然后从pq中取出编号最小的点

以这个点为起点,继续更新能够到的城市

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstring>
 4 #include <vector>
 5 #include <queue>
 6 using namespace std;
 7 const int N = 1e5 + 2;
 8 vector<int> ans;
 9 priority_queue<int, vector<int>, greater<int>> pq;
10 vector<int> sides[N];
11 int n, k;
12 int vis[N];
13 void bfs(int s)
14 {
15     queue<int> q;
16     q.push(s);
17     vis[s] = 0;
18     while (q.size())
19     {
20         int x = q.front();
21         q.pop();
22         if (vis[x] >= k)
23             continue;
24         for (int i = 0; i < sides[x].size(); i++)
25         {
26             int node = sides[x][i];
27             if (vis[node] == -1)
28             {
29                 vis[node] = vis[x] + 1;
30                 q.push(node);
31                 pq.push(node);
32             }
33             else if (vis[node] >= vis[x] + 1)
34             {
35                 vis[node] = vis[x] + 1;
36                 q.push(node);
37             }
38         }
39     }
40 }
41 int main()
42 {
43     cin >> n >> k;
44     for (int i = 1; i <= n - 1; i++)
45     {
46         int a, b;
47         scanf("%d%d", &a, &b);
48         sides[a].push_back(b), sides[b].push_back(a);
49     }
50     memset(vis, -1, sizeof(vis));
51     pq.push(1);
52     while (pq.size())
53     {
54         int x = pq.top();
55         pq.pop();
56         ans.push_back(x);
57         bfs(x);
58     }
59     for (int i = 0; i < ans.size(); i++)
60         cout << ans[i] << " ";
61     return 0;
62 }

算法关键:

 

上次从点11开始,然后扩展点

然后,下一次到了点4开始,注意这一次vis是给点13,点6,点19都打上了标记,

但是如果我们在bfs()如果看到了vis标记过了的就不去看会有大问题

比如这个案例中:

点1是上次在点11是无法扩展到的,但是在点4就可以扩展到

如果我们看到了vis标记过了的就不去看,那么在点4,点1就无法扩展了

 

解决方案:

 

 

posted @ 2022-10-04 10:28  次林梦叶  阅读(34)  评论(0)    收藏  举报