Edge to the Root
Time Limit: 1 Second
Memory Limit: 131072 KB
Given a tree with n vertices, we want to add an edge between vertex 1 and vertex x, so that the sum of d(1, v) for all vertices v in the tree is minimized, where d(u, v) is the minimum number of edges needed to pass from vertex u to vertex v. Do you know which vertex x we should choose?
Recall that a tree is an undirected connected graph with n vertices and n – 1 edges.
Input
There are multiple test cases. The first line of input contains an integer T, indicating the number of test cases. For each test case:
The first line contains an integer n (1 ≤ n ≤ 2 × 105), indicating the number of vertices in the tree.
Each of the following n – 1 lines contains two integers u and v (1 ≤ u, v ≤ n), indicating that there is an edge between vertex u and v in the tree.
It is guaranteed that the given graph is a tree, and the sum of n over all test cases does not exceed 5 × 105. As the stack space of the online judge system is not very large, the maximum depth of the input tree is limited to about 3 × 104.
We kindly remind you that this problem contains large I/O file, so it’s recommended to use a faster I/O method. For example, you can use scanf/printf instead of cin/cout in C++.
Output
For each test case, output a single integer indicating the minimum sum of d(1, v) for all vertices v in the tree (NOT the vertex x you choose).
Sample Input
2 6 1 2 2 3 3 4 3 5 3 6 3 1 2 2 3
Sample Output
8 2
Hint
For the first test case, if we choose x = 3, we will have
d(1, 1) + d(1, 2) + d(1, 3) + d(1, 4) + d(1, 5) + d(1, 6) = 0 + 1 + 1 + 2 + 2 + 2 = 8
It’s easy to prove that this is the smallest sum we can achieve.
Author:
WENG, Caizhi
Source: The 17th Zhejiang University Programming Contest Sponsored by TuSimple
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x, y) memset(x, y, sizeof(x))
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; }
const int N = 2e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f;
template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; }
int casenum, casei;
int n;
vector<int>a[N]; //边
int sz[N]; //子树大小
int dep[N]; //节点到root的距离
LL sumdis; //初始距离之和
int b[N]; //当前递归栈序列
LL ans;
void getbg(int x, int fa)
{
sumdis += dep[x];
sz[x] = 1;
for (auto y : a[x])if (y != fa)
{
dep[y] = dep[x] + 1;
getbg(y, x);
sz[x] += sz[y];
}
}
void dfs(int x, int fa, int p, LL tmp)
{
b[dep[x]] = x;
if (dep[x] == 2)
{
p = dep[x];
tmp -= sz[x];
}
else if(p)
{
int u = b[p];
int d1 = dep[u];
int d2 = dep[x] - dep[u] + 1;
if (d1 <= d2)++p;
}
gmin(ans, tmp);
for (auto y : a[x])if (y != fa)
{
if (!p)
{
dfs(y, x, p, tmp);
}
else
{
dfs(y, x, p, tmp - sz[y] + sz[b[p]] - sz[y]);
}
}
}
int main()
{
scanf("%d", &casenum);
for (casei = 1; casei <= casenum; ++casei)
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)a[i].clear();
for (int i = 1; i < n; ++i)
{
int x, y; scanf("%d%d", &x, &y);
a[x].push_back(y);
a[y].push_back(x);
}
sumdis = dep[1] = 0; getbg(1, 0);
ans = sumdis; dfs(1, 0, 0, sumdis);
printf("%lld\n", ans);
}
return 0;
}
/*
【题意】
有一棵树,树以1为根,有n(2e5)个节点。
我们希望在树上增加任意的一条边,使得——
dis(1,1) + dis(1,2) + ... + dis(1,n) 尽可能小
【分析】
一开始有些东西是有必要求得的。
dep[x]表示节点x的深度,其在数值上也等于初始节点x到root的距离,root的深度设为0,
sz[x]表示节点x的子节点数量(包括x)
这道题乍一看似乎很难,我们可以先讨论一下——
如果我们这条边是连向root或者root的各个子节点的,各点的距离没有变化。
如果我们这条边是连向dep[x]的节点的话,
x的所有子节点的对dis的贡献-= (dep[x] - 1)
而root->x路径上的节点,每个节点y以及y相连的不在该路径上的节点,都会有同样的距离变化。
所谓距离变化,指的是——这些节点到根的距离可能会一定程度地变小。
这个距离变化是呈现等差数列的形式的,于是我们很难一步到位求得。
于是我们不妨可以考虑在转移上下功夫。
我们假设已经求出了当前连到x的距离之和DIS,而如今要从x转移到y。
第一个变化
对于sz[y]的每一个节点,其到根的距离 -= 1(要求dep[y] >= 2)
然而,对于现在要通过新加边走向root的所有节点,其到根的距离 += 1(肯定要通过新加边更近才行啦)
于是我们还维护,哪些节点是通过新加边走向root的。就是看看哪些节点从新加边走向根节点严格更近。
于是我们要维护一个特殊节点,该节点是刚好走新加边会更近1步的,其可以通过dfs序列与指针维护。
*/