给定一棵 \(n\) 个节点的树,点编号为 \(1...n\)。\(Q\) 次询问,每次询问给定一个点集 \(S\),令 \(f(u)=\max\limits_{v\in S}dist(u,v)\) ,你需要求出\(\min\limits_{u=1...n}f(u)\)。其中 \(dist(u,v)\) 表示树上路径 \((u,v)\) 的边数。
链接:https://ac.nowcoder.com/acm/contest/7141/A
题意转化为找出一个点,使得其到点集中的点的距离的最大值最小。对于点集组成的树而言,该点肯定在其直径的中点左右,那么答案就是 \(\lceil \frac{直径}{2} \rceil\)。因此,需要求出直径的长度。因为点集中深度最深的点一定是直径的一个端点,因此,只要找到该点,然后遍历其它的点,通过 \(LCA\) 求出两点间距离,取最大值即可。
\(LCA\) 通过 \(ST\) 表实现,这样可以实现 \(O(1)\) 查询,用倍增可能会超时。
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=3e5+5;
const int M=1e6+6;
const int mak=25;
vector<int>pic[N];
int depth[N],f[N<<1][mak],rnk[N],n,cnt;
int s[M];
void read(int &x)
{
x=0;
int f=1;
char ch=getchar();
while(ch<‘0‘||ch>‘9‘)
{
if(ch==‘-‘) f=-1;
ch=getchar();
}
while(ch>=‘0‘&&ch<=‘9‘)
{
x=x*10+ch-‘0‘;
ch=getchar();
}
x*=f;
}
void dfs(int u,int p,int d)
{
depth[u]=d;
rnk[u]=++cnt;
f[cnt][0]=u;
for(int i=0;i<pic[u].size();i++)
{
int v=pic[u][i];
if(v==p) continue;
dfs(v,u,d+1);
f[++cnt][0]=u;
}
}
void init()
{
dfs(1,0,0);
int len=(int)log2(1.0*cnt);
for(int k=1;k<=len;k++)
{
for(int i=1;i+(1<<k)-1<=cnt;i++)
{
int a=f[i][k-1],b=f[i+(1<<(k-1))][k-1];
if(depth[a]<depth[b]) f[i][k]=a;
else f[i][k]=b;
}
}
}
int lca(int u,int v)
{
int l=rnk[u],r=rnk[v];
if(l>r) swap(l,r);
int len=(int)log2(1.0*(r-l+1));
int a=f[l][len],b=f[r-(1<<len)+1][len];
if(depth[a]<depth[b]) return a;
else return b;
}
int get_dis(int u,int v)
{
return depth[u]+depth[v]-2*depth[lca(u,v)];
}
int main()
{
int x,y,m,q;
read(n);
for(int i=1;i<n;i++)
{
read(x),read(y);
pic[x].pb(y);
pic[y].pb(x);
}
init();
read(q);
while(q--)
{
read(m);
int ans=0,dm=0,d=0;
for(int i=1;i<=m;i++)
{
read(s[i]);
if(depth[s[i]]>dm)
dm=depth[s[i]],d=s[i];
}
for(int i=1;i<=m;i++)
{
if(s[i]==d) continue;
ans=max(ans,get_dis(d,s[i]));
}
printf("%d\n",(ans+1)/2);
}
return 0;
}
原文:https://www.cnblogs.com/1024-xzx/p/13532109.html