D. A Wide, Wide Graph
D. A Wide, Wide Graph
You are given a tree (a connected graph without cycles) with vertices.
Consider a fixed integer . Then, the graph is an undirected graph with vertices, where an edge between vertices and exists if and only if the distance between vertices and in the given tree is at least .
For each from to , print the number of connected components in the graph .
Input
The first line contains the integer () — the number of vertices in the graph.
Each of the next lines contains two integers and (), denoting an edge between vertices and in the tree. It is guaranteed that these edges form a valid tree.
Output
Output integers: the number of connected components in the graph for each from to .
Examples
input
6 1 2 1 3 2 4 2 5 3 6
output
1 1 2 4 6 6
input
5 1 2 2 3 3 4 3 5
output
1 1 3 5 5
Note
In the first example: If , the graph has an edge between each pair of vertices, so it has one component. If , the graph has only edges and , so the graph has components.
In the second example: when or the graph has one component. When the graph splits into components: one component has vertices , and , and two more components contain one vertex each. When or each vertex is a separate component.
解题思路
容易发现当时,必然只有一个连通块。当时,有个连通块,即不存在任何一条边,这是因为树中任意两点距离不超过。可以发现随着的增大中连通块的数量会逐渐增加,即中的边会逐渐减少。因此我们可以从大到小枚举,这就等价于每次在中加边,然后求有多少个连通块,这就比删边好处理了。
我们知道树中最长的路径为树的直径,假设直径长度为,因此如果,那么中必然不存在任何一条边,而当,此时中所有直径的端点构成一个连通块。而除了端点(端点指直径的端点,下同)之外,其他的点只有在时才能被连边。那么对于某个点,枚举到多少才能与其他的点连一条边呢?假设从点出发最远能走到点(此时必然是一个端点,证明参考此处链接),路径长度为,那么当时和连一条边。因此对于某个点,只有当至少枚举(从大到小枚举)到能走到的最远距离时,才能被连一条边加入到某个连通块中。
然后这里有个性质,假设最远能走到的点构成的点集,那么任意一条直径的两个端点和,必然满足或(也可能同时满足)。下面来证明这个性质,证明方法与链接证明树的直径的方法相似。
假设树的直径的两个端点为和,求最远能走到的点,分情况讨论:
情况:是直径上点。反证法最远能走到的点为(必然不在直径上)而不是和。
那么有,。从而有,即是一条更长的直径,这就与端点所构成的路径是直径矛盾了。
情况:不是直径上点。反证法最远能走到的点为而不是和。
那么有,。由于,从而有,从而有,有。即是一条更长的直径,这就与端点所构成的路径是直径矛盾了。
有了这个性质后,当枚举到能走到的最远距离时,在中必然至少会与端点和中的一个连一条边。又因为和已经在一个连通中,因此必然会在端点所在的连通块中。即当枚举到能走到的最远距离时,必然会在端点所在的连通块。
因此我们可以先通过两遍找到任意一条直径的两个端点和,然后分别求其余点到和的最远距离,也就是从其余点出发能走到的最长距离。然后从大到小枚举,在这个过程中把所有最长距离为的点都加入到端点所在的连通块中,由于一开始每一个点都是一个连通块,这就等价于连通块数量减。
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int N = 1e5 + 10, M = N * 2; 5 6 int head[N], e[M], ne[M], idx; 7 int d1[N], d2[N]; 8 int ans[N]; 9 10 void add(int v, int w) { 11 e[idx] = w, ne[idx] = head[v], head[v] = idx++; 12 } 13 14 void dfs(int u, int pre, int *d) { 15 for (int i = head[u]; i != -1; i = ne[i]) { 16 if (e[i] != pre) { 17 d[e[i]] = d[u] + 1; 18 dfs(e[i], u, d); 19 } 20 } 21 } 22 23 int main() { 24 int n; 25 scanf("%d", &n); 26 memset(head, -1, sizeof(head)); 27 for (int i = 0; i < n - 1; i++) { 28 int v, w; 29 scanf("%d %d", &v, &w); 30 add(v, w), add(w, v); 31 } 32 dfs(1, -1, d1); 33 int x = max_element(d1 + 1, d1 + n + 1) - d1; // 找到其中一个端点x 34 d1[x] = 0; 35 dfs(x, -1, d1); // 找到另外一个端点y,同时求x到其他点的距离 36 int y = max_element(d1 + 1, d1 + n + 1) - d1; 37 dfs(y, -1, d2); // 求y到其他点的距离 38 for (int i = 1; i <= n; i++) { 39 d1[i] = max(d1[i], d2[i]); // 求每个点到端点x和y的最长距离 40 } 41 sort(d1 + 1, d1 + n + 1); 42 ans[n] = n; // k=n必然有n个连通块 43 for (int i = n - 1, j = n - 1; i; i--) { // 从大到小枚举k 44 ans[i] = ans[i + 1]; 45 while (j && d1[j] == i) { // 把所有最长距离为k的点找出来 46 j--; 47 ans[i]--; // 连通块数量减1 48 } 49 } 50 for (int i = 1; i <= n; i++) { 51 printf("%d ", ans[i]); 52 } 53 54 return 0; 55 }
参考资料
Editorial of Codeforces Round #862 (Div. 2):https://codeforces.com/blog/entry/114644
Codeforces Round 862 (Div. 2) D~E:https://zhuanlan.zhihu.com/p/619152050
Codeforces Round 862 (Div. 2) A - D:https://zhuanlan.zhihu.com/p/618971125
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17285180.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-04-04 牛奶工厂