每日一题 7月8日 Alliances 点到树上点集的连通网的最短距离

题目链接:https://ac.nowcoder.com/acm/problem/13950
题目大意:
树上有k个帮派,每个帮派会占据一些城市。他们控制的点除了自己占领的点。还有再加上这些城市两两之间路径上的所有城市。

有时帮派之间会结盟,这样联盟的点集为这些帮派的点集的并集。他们控制的点除了自己占领的点。还有再加上这些城市两两之间路径上的所有城市。

现在有Q个询问。每次询问选择一个点作为首都。和一些帮派。我们只考虑这些被选择的帮派,并且他们结盟。首都离联盟控制的点最近距离为多少?

图片说明

思路:我们先对所有的帮派求一个LCA。保存在LCA[i]。每次询问的帮派x1, x2, ...., xm。对LCA[x1], LCA[x2], ... , LCA[xm]求一个LCA。
1.如果首都root没有在LCA的子树里。那么最短距离为dis(root, LCA)。
2.如果在LCA的子树里面。
(1):如果root的子树有帮派原先占领的点。那么这个点到LCA一定经过root。距离为0。我们可以对每个帮派的点的dfs序排序二分。那么dfs序比in[root]大并且dfs最小的点最有可能是root的子树
(2):如果root的子孙没有被占领。那么可以把dfs序中他左边和右边离他最近的两个点拿出来和首都求LCA,这两个LCA里面一定有一个是离它最近的。(情况2其实可以合并在3里面处理)

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

vector<int> G[500005];
int in[500005], out[500005], T=0;
struct LCA{
    int d[500005], fa[500005][22], lg[500005];
    void init(int n, int root){//预处理
        for(int i = 1; i <= n; ++i){
            lg[i] = lg[i-1] + (1 << lg[i-1] == i);
        }
        dfs(root, 0);
    }
    void dfs(int now, int father){
        in[now]=++T;
        fa[now][0]=father; d[now]=d[father]+1;
        for(int i=1; i<=lg[d[now]]; ++i){
            fa[now][i]=fa[fa[now][i-1]][i-1];
        }
        for(auto x: G[now]){
            if(x!=father){
                dfs(x, now);
            }
        }
        out[now]=T;
    }
    int lca(int x, int y){//LCA
        if(d[x]<d[y]) swap(x, y);
        while(d[x]>d[y]){
            x=fa[x][lg[d[x]-d[y]]-1];
        }
        if(x==y) return x;
        for(int k=lg[d[x]]-1; k>=0; --k){
            if(fa[x][k]!=fa[y][k]){
                x=fa[x][k], y=fa[y][k];
            }
        }
        return fa[x][0];
    }
    int dis(int x, int y){
        return d[x]+d[y]-2*d[lca(x, y)];
    }
}lca;

vector<int> b[500005], xw;
vector<pair<int, int> > dset[500005];//每个帮派的dfs序和点
int LCA[500005];

int slove(int root){
    int Lca=0;
    for(auto x: xw){
        if(Lca==0) Lca=LCA[x];
        else Lca=lca.lca(Lca, LCA[x]);
    }
    if(in[root]<in[Lca]||in[root]>out[Lca]){//在LCA的子树外
        return lca.dis(Lca, root);
    }
    int ans=1<<30;
    pair<int, int> pos1={0 , 0}, pos2={1<<30, 0};//在LCA的子树内
    for(auto x: xw){
        int pos=lower_bound(dset[x].begin(), dset[x].end(), make_pair(in[root], 0))-dset[x].begin();
        if(pos<=dset[x].size()-1){//求最大的小等于dfs[root]的点
            if(dset[x][pos].first<pos2.first){
                pos2=dset[x][pos];
            }
        }
        if(pos>0){//求最小的大等于dfs[root]的点
            pos--;
            if(dset[x][pos].first>pos1.first){
                pos1=dset[x][pos];
            }
        }
    }
    if(pos1.second){
        ans=min(ans, lca.dis(root, lca.lca(root, pos1.second)));
    }
    if(pos2.second){
        ans=min(ans, lca.dis(root, lca.lca(root, pos2.second)));
    }
    return ans;
}

int main(){
    int n, x, y, k; scanf("%d", &n);
    for(int i=1; i<n; i++){
        scanf("%d%d", &x, &y);
        G[x].push_back(y);
        G[y].push_back(x);
    }

    lca.init(n+3, 1);
    scanf("%d", &k);
    for(int i=1; i<=k; i++){
        int m; scanf("%d", &m);
        while(m--){
            scanf("%d", &x);
            b[i].push_back(x);
        }
    }
    for(int i=1; i<=k; i++){
        int Lca=0;
        for(auto x: b[i]){
            dset[i].push_back({in[x], x});
            if(Lca==0) Lca=x;
            else Lca=lca.lca(Lca, x);
        }
        LCA[i]=Lca;
        sort(dset[i].begin(), dset[i].end());
    }
    int q; scanf("%d", &q);
    while(q--){
        int root, m;
        scanf("%d%d", &root, &m);
        xw.clear();
        for(int i=1; i<=m; i++){
            scanf("%d", &x);
            xw.push_back(x);//每次询问的帮派的集合
        }
        printf("%d\n", slove(root));
    }

    return 0;
}
全部评论

相关推荐

10-21 23:48
蚌埠坦克学院
csgq:可能没hc了 昨天一面完秒挂
点赞 评论 收藏
分享
09-20 09:17
已编辑
中国矿业大学 机械设计师
大连理工大学机械工程师:拖拉机研究院1.5
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务