题目链接
题意:
给 一 棵 n 个 点 的 树 , 点 的 编 号 为 1 , 2 , 3 , … … , n 给一棵n个点的树,点的编号为1,2,3,……,n 给一棵n个点的树,点的编号为1,2,3,……,n
q 次 询 问 , 每 次 询 问 给 定 一 个 点 集 S q次询问,每次询问给定一个点集S q次询问,每次询问给定一个点集S
令 f ( u ) 为 u 到 点 集 内 点 的 最 大 距 离 令f(u)为u到点集内点的最大距离 令f(u)为u到点集内点的最大距离
求 m i n u = 1.. n f ( u ) 求min_{u=1..n}f(u) 求minu=1..nf(u)
题解:
n < = 3 e 5 , ∑ S < = 1 e 6 n<=3e5,\sum{S}<=1e6 n<=3e5,∑S<=1e6
题 意 转 化 : 题意转化: 题意转化:
找 到 一 个 点 到 点 集 点 的 最 大 距 离 最 小 找到一个点到点集点的最大距离最小 找到一个点到点集点的最大距离最小
那 么 肯 定 要 找 点 集 最 远 的 两 个 点 那么肯定要找点集最远的两个点 那么肯定要找点集最远的两个点
然 后 找 一 个 点 到 他 俩 距 离 相 同 或 者 差 为 1 然后找一个点到他俩距离相同或者差为1 然后找一个点到他俩距离相同或者差为1
所 以 只 要 将 点 集 的 直 径 平 分 即 可 所以只要将点集的直径平分即可 所以只要将点集的直径平分即可
那 么 最 后 答 案 就 是 c e i l ( 直 径 / 2 ) 那么最后答案就是ceil(直径/2) 那么最后答案就是ceil(直径/2)
可 以 考 虑 用 虚 树 建 树 找 直 径 可以考虑用虚树建树找直径 可以考虑用虚树建树找直径
但 是 可 以 通 过 求 直 径 的 性 质 但是可以通过求直径的性质 但是可以通过求直径的性质
直 径 的 一 个 端 点 肯 定 是 深 度 最 深 的 点 直径的一个端点肯定是深度最深的点 直径的一个端点肯定是深度最深的点
找 到 这 个 点 , 然 后 利 用 L C A 求 出 到 其 他 点 的 最 大 距 离 , 除 二 即 可 找到这个点,然后利用LCA求出到其他点的最大距离,除二即可 找到这个点,然后利用LCA求出到其他点的最大距离,除二即可
AC代码
/*
Author : zzugzx
Lang : C++
Blog : blog.csdn.net/qq_43756519
*/
#include
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(), (x).end()
#define endl '\n'
#define SZ(x) (int)x.size()
#define mem(a, b) memset(a, b, sizeof(a))
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const double eps = 1e-6;
const double pi = acos(-1.0);
const int maxn = 1e6 + 10;
const int N = 1e2 + 5;
const ll inf = 0x3f3f3f3f;
const ll oo = 8e18;
const int dir[][2]={
{
0, 1}, {
1, 0}, {
0, -1}, {
-1, 0}, {
1, 1}, {
1, -1}, {
-1, 1}, {
-1, -1}};
int n,depth[maxn], f[maxn][50];
int from[maxn], to[maxn << 1], nxt[maxn << 1], cnt,Log[maxn];
//链式前向星加边
void addEdge (int u, int v) {
to[++cnt] = v, nxt[cnt] = from[u], from[u] = cnt;
}
//计算深度&计算祖先
void dfs (int u, int fa) {
depth[u] = depth[fa] + 1;
for (register int i = 1; i <= Log[n]; ++i) {
if ((1 << i) > depth[u]) break;
f[u][i] = f[f[u][i - 1]][i - 1];
}
for (register int i = from[u]; i; i = nxt[i]) {
ll v = to[i];
if (v == fa) continue;
f[v][0] = u;
dfs (v, u);
}
}
//计算LCA
inline int LCA (int x, int y) {
if (depth[x] < depth[y]) swap(x, y);
//我们默认x为更深的那个点
for(register int i = Log[n] ; i >= 0 ; --i)
if(depth[x] - (1 << i) >= depth[y]) x = f[x][i];
//将x跳到和y同一深度上
if (x == y) return x;
for (register int i = Log[n]; i >= 0; --i)
if (f[x][i] != f[y][i])
x = f[x][i], y = f[y][i];
//一起向上跳
return f[x][0];
//不难看出,此时两个点均在其LCA的下方,往上跳一次即可
}
void init(){
Log[0] = -1;
cnt = 0;
for(int i = 1; i <= n; i++){
from[i] = 0,depth[i] = 0;
for(int j = 0; j < 50; j++)
f[i][j] = 0;
}
for (register int i = 1, u, v; i < n; ++i) {
cin >> u >> v;
addEdge (u, v); addEdge(v, u);
Log[i] = Log[i >> 1] + 1;
}
Log[n] = Log[n >> 1] + 1;
dfs(1,0);
}
int dis(int p , int q){
return depth[p] + depth[q] - 2 * depth[LCA(p , q)];}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
cin >> n;
init();
int _;
cin >> _;
while (_--) {
int k, x, y = 0, ans = 0;
cin >> k;
vector<int> v;
for(int i = 1; i <= k; i++) {
cin >> x;
if (depth[x] > depth[y]) y = x;
v.pb(x);
}
for (auto i : v) ans = max(ans, dis(y, i));
cout << (ans + 1) /2 << endl;
}
return 0;
}