The only difference between this problem and D1 is the bound on the size of the tree.
You are given an unrooted tree with nn vertices. There is some hidden vertex xx in that tree that you are trying to find.
To do this, you may ask kk queries v1,v2,…,vk where the vivi are vertices in the tree. After you are finished asking all of the queries, you are given kk numbers d1,d2,…,dk, where didi is the number of edges on the shortest path between vivi and xx. Note that you know which distance corresponds to which query.
What is the minimum kk such that there exists some queries v1,v2,…,vk that let you always uniquely identify x (no matter what xx is).
Note that you don't actually need to output these queries.
Input
Each test contains multiple test cases. The first line contains the number of test cases t (1≤t≤104). Description of the test cases follows.
The first line of each test case contains a single integer n (1≤n≤2⋅105) — the number of vertices in the tree.
Each of the next n−1 lines contains two integers xx and y (1≤x,y≤n), meaning there is an edges between vertices x and y in the tree.
It is guaranteed that the given edges form a tree.
It is guaranteed that the sum of nn over all test cases does not exceed 2⋅105.
Output
For each test case print a single nonnegative integer, the minimum number of queries you need, on its own line.
Example
input
3 1 2 1 2 10 2 4 2 1 5 7 3 10 8 6 6 1 1 3 4 7 9 6
output
0 1 2
Note
In the first test case, there is only one vertex, so you don't need any queries.
In the second test case, you can ask a single query about the node 1. Then, if x=1, you will get 0, otherwise you will get 1.
题意: 给出一棵树,这颗树上存在一个待查找的结点x,现在你可以做k次询问,每次询问可以查询一个点到x的距离,输出能确定x编号的最少查询次数。
分析: 对于一棵树而言,通过查询所有叶子结点一定可以确定x的位置,这是因为查询叶子结点优于查询其父节点,而查询父节点优于查询祖先结点,因为父节点并不能确定x是否为其下图范围中的结点。
而查询叶子结点不能确定x是否为下图范围中的结点,比前者范围要更小。
但是全部查一遍叶子结点是不必要的,对于一个分叉结点(度 >= 3)来说,设其有n个子结点,只需要查询其n-1个子结点就能分辨出x是否位于其子结点中,所以最终求解方法就是从每个子结点开始遍历,一旦遇到分叉结点就停止,如果该分叉结点尚未访问就标记为已访问过,如果该分叉结点被访问过就将答案加1,由于最终每个子结点都是遍历到其最近的分叉结点停止,所以总共相当于遍历了树一遍,时间复杂度为O(n)。
具体代码如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
using namespace std;
vector<int> g[200005];
bool vis[200005];
int ans;
void dfs(int now){
if(g[now].size() >= 3)
return;
for(int i = 0; i < g[now].size(); i++){
int to = g[now][i];
if(!vis[to]){
vis[to] = true;
dfs(to);
}
else if(g[to].size() >= 3) ans++;
}
}
signed main()
{
int T;
cin >> T;
while(T--){
int n;
scanf("%d", &n);
ans = 0;
for(int i = 1; i <= n; i++){
g[i].clear();
vis[i] = false;
}
for(int i = 1; i < n; i++){
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
for(int i = 1; i <= n; i++)
if(g[i].size() == 1)
dfs(i);
if(ans == 0 && n != 1) ans = 1;
printf("%d\n", ans);
}
return 0;
}