Wannafly挑战赛1 C-MMSet2(树的直径)

本文详细解析了在树形结构中求解点集直径的算法,通过预处理倍增数组和点到根节点距离,利用两次深度优先搜索(DFS)找到树的直径,并通过点集的特性得出直径/2上取整的最优解。适用于多次询问,每个点到给定点集的最大值的最小值问题。

题目链接:C-MMSet2

题意:多次询问,每个点到给定点集的最大值的最小值。
设点集为 s s s, f ( u ) = m a x v ∈ s ( d i s ( u , v ) ) f(u)=max_{v\in s}(dis(u,v)) f(u)=maxvs(dis(u,v)),求 m i n 1 < = u < = n f ( u ) min_{1<=u<=n}f(u) min1<=u<=nf(u)

考虑点集只有1个点,那该点到该点的距离为0,就是要求的答案。
考虑点集只有两个点x,y,那这两个点的路径上的点到这两点的最大值是不是一定大于等于 ⌈ d i s ( x , y ) / 2 ⌉ \lceil dis(x,y)/2 \rceil dis(x,y)/2
扩展到多个点,符合答案要求的点的一端一定是点集其中的点,另一端一定是点集任两点路径上的点吧。

结论:点集的直径/2的上取整即为答案。

随便选一个点作为根,预处理倍增数组,以及各点到根节点的距离。
注意到所有的询问点集加起来为 1 0 6 10^6 106,说明 n ∗ l o g 2 ( n ) n*log_2(n) nlog2(n)的复杂度可行,对于边长度相同的树的直径,通常可用两次dfs求得,也即第一次从根节点出发找一个最远点,再从最远点出发找一个最远点。各节点与根节点的距离已经预处理出来了,那么遍历点集即可知道最远点x,然后再遍历一次点集,求 m a x ( d i s [ x ] + d i s [ y ] − 2 ∗ d i s [ l c a ] ) max(dis[x]+dis[y]-2*dis[lca]) max(dis[x]+dis[y]2dis[lca])

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=3e5+7;

bool vis[maxn];

struct Edge{
    int v,next;
}edge[maxn<<1];

int head[maxn],top;

void add(int u,int v){
    edge[top].v=v;
    edge[top].next=head[u];
    head[u]=top++;
}

vector<int> vv;
int dis[maxn];
int dep[maxn];
int fa[maxn][20];
const int ci=19;
void dfs(int u,int faa,int val){
    int v;
    dis[u]=val;
    for(int i=head[u];i!=-1;i=edge[i].next){
        v=edge[i].v;
        if(v==faa) continue;
        dep[v]=dep[u]+1;
        fa[v][0]=u;
        for(int j=1;j<=ci;++j) fa[v][j]=fa[fa[v][j-1]][j-1];
        dfs(v,u,val+1);
    }
}

int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=ci;i>=0;--i)
        if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
    if(x==y) return x;
    for(int i=ci;i>=0;--i)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int main(){
    int n,u,v,siz,x;
    memset(head,-1,sizeof(head));
    top=0;
    int q;
    scanf("%d",&n);
    for(int i=1;i<n;++i){
        scanf("%d%d",&u,&v);
        add(u,v),add(v,u);
    }
    dep[1]=1;
    dfs(1,0,0);
    scanf("%d",&q);
    while(q--){
        int maxx=0,mm;
        scanf("%d",&siz);
        for(int i=1;i<=siz;++i){
            scanf("%d",&x);
            vv.push_back(x);
            if(dis[x]>maxx){
                maxx=dis[x];
                mm=x;
            }
        }
        if(siz==1){
            printf("0\n");
        }
        else{
            maxx=0;
            u=mm;
            for(int i=0;i<vv.size();++i){
                v=vv[i];
                int z=lca(u,v);
                maxx=max(maxx,dis[u]+dis[v]-2*dis[z]);
            }
            printf("%d\n",maxx/2+(maxx&1?1:0));
        }
        vv.clear();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值