漂亮的公园 O(|S|)的复杂度求点集S的直径
题目
漂亮的公园 (https://ac.nowcoder.com/acm/problem/18391)
题目描述
小N所在城市有n个漂亮的公园。有恰好n-1条双向道路连接这n个公园,保证公园间相互可以通过道路到达。每个公园i都有一个专属的属性c[i],表示这个公园的特色。
现在小N有q个疑问。每次他会有两个特定的特色x和y(这两个数可能相同)。他想知道,假如他随便选取一个特色为x的公园出发,必须走到一个特色为y的公园结束,在最优情况下,他最远能走过多少条道路。换句话说,枚举所有的满足c[p] = x的公园p,所有满足c[q] = y的公园q,求出p,q距离的最大值。
输入描述:
第一行两个整数n和q。
第二行n个整数,第i个整数表示第i个公园对应的特色c[i]。
接下来n-1行,每行两个整数u和v,表示有一条连接公园u和公园v的道路。
接下来q行,每行两个整数x和y,代表小N的一个疑问。
数据保证:n ≤ 10^5, q ≤ 10^5, 1 ≤ u, v ≤ n, 0 ≤ c[i], x, y ≤ 10^9
输出描述:
输出共q行,每行对于小N的一个疑问的答案。注意,假如一个疑问中,不存在一个公园特色为x,或不存在一个公园特色为y,那么输出答案0。代表小N一条边都走不了。
输入
10 4
9 8 9 8 9 8 7 7 8 7
4 5
5 10
10 3
3 9
9 1
1 8
5 7
10 6
8 2
5 8
8 8
7 9
8 9
输出
0
7
5
6
思路
最长距离我们自然联想到树的直径。如果我们把每个属性集合的直径都求出来。那么求p,q的直径。
假设p的直径(x1, y1),q的直径(x2, y2)。那么p集到q集的最远距离可能的组合为(x1, x2)
(x1, y2), (y1, x2), (y1, y2),我们取一个最大值就可以了。
现在的问题是我们怎么求每个属性的直径。我们把每个点的加入vector。根据直径的性质:距离点集任意点的最远点一定是直径的一个端点。
那么复杂度为2|s|就可以求出s集合的直径。求距离可以倍增+LCA。那么就可以n*logn求出所有的属性集合的直径。
回答每个询问O(1)就可以了
#include<bits/stdc++.h>
using namespace std;
struct edge{
int to, nxt;
}e[100005<<1];
int head[100005], lg[200005], cut=0;
struct Tree_lca{
int f[100005][22], deep[100005], in[100005], out[100005], T=0;
void init(){
memset(head, -1, sizeof(head));
cut=T=0;
}
void addedge(int x, int y){
e[++cut]={y, head[x]};
head[x]=cut;
}
void dfs(int u, int fa){
f[u][0]=fa; in[u]=++T; deep[u]=deep[fa]+1;
for(int i=1; i<=lg[deep[u]]; i++){
f[u][i]=f[f[u][i-1]][i-1];
}
for(int i=head[u]; i!=-1; i=e[i].nxt){
int to=e[i].to;
if(to!=fa){
dfs(to, u);
}
}
out[u]=++T;
}
int LCA(int x, int y){
if(deep[x]<deep[y]) swap(x, y);
int d=deep[x]-deep[y];
for(int i=0; d; i++){
if(d&(1<<i)){
x=f[x][i];
d-=(1<<i);
}
}
if(x==y) return x;
for(int i=lg[deep[y]]; i>=0; i--){
if(f[x][i]!=f[y][i]){
x=f[x][i], y=f[y][i];
}
}
return f[x][0];
}
int dis(int x, int y){
return deep[x]+deep[y]-2*deep[LCA(x, y)];
}
int getans(int x1, int y1, int x2, int y2){
int pos[4]={x1, y1, x2, y2};
int mx=0;
for(int i=0; i<2; i++){
for(int j=2; j<4; j++){
mx=max(mx, dis(pos[i], pos[j]));
}
}
return mx;
}
}Tree;
int c[100005], d[100005][2];
vector<int> g[100005];
struct LSH{//离散化
int b[100005];
unordered_map<int, int> mp;//范围
int lsh(int a[], int n){//得到离散化后不同元素的个数
mp.clear();
for(int i=1; i<=n; i++) b[i]=a[i];
sort(b+1, b+n+1);
int cnt=unique(b+1, b+n+1)-b-1;
for(int i=1; i<=n; i++){
int x=a[i];
a[i]=lower_bound(b+1, b+cnt+1, a[i])-b;
mp[x]=a[i];
}
return cnt;
}
int id(int x){//得到原树
return mp[x];
}
}Lsh;
int main() {
lg[1]=1;
for(int i=2; i<=100005; i++){
lg[i]=lg[i>>1]+1;
}
int n, q; scanf("%d%d", &n, &q);
for(int i=1; i<=n; i++){
scanf("%d", &c[i]);
}
int m=Lsh.lsh(c, n);
for(int i=1; i<=n; i++){
g[c[i]].push_back(i);
}
Tree.init();
for(int i=1;i<=n-1; i++){
int x, y; scanf("%d%d", &x, &y);
Tree.addedge(x, y); Tree.addedge(y, x);
}
Tree.dfs(1, 0);
for(int i=1; i<=m; i++){
d[i][0]=d[i][1]=g[i][0];
int d1=Tree.dis(d[i][0], d[i][1]);
for(int j=1; j<g[i].size(); j++){
int x=g[i][j];
int d2=Tree.dis(d[i][0], x), d3=Tree.dis(d[i][1], x);
if(d2<d3) swap(d2,d3),swap(d[i][0],d[i][1]);
if(d2>d1) d[i][1]=g[i][j],d1=Tree.dis(d[i][0],d[i][1]);
}
}
for(int i=1; i<=q; i++){
int x, y; scanf("%d%d", &x, &y);
x=Lsh.mp[x], y=Lsh.mp[y];
//printf("%d %d %d %d\n", d[x][0], d[x][1], d[y][0], d[y][1]);
if(x==0||y==0){
printf("0\n");
}
else{
printf("%d\n", Tree.getans(d[x][0], d[x][1], d[y][0], d[y][1]));
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)