信息学奥赛一本通1759:采访计划

原题链接:https://ybt.ssoier.cn/problem_show.php?pid=1759

【题目描述】
公元2044年,人类将进入宇宙纪元。L国有n个星球,分别编号为1到n,每一星球上有一个球长。因为历史的长期积淀,第i个星球上还有一位编号为i的德高望重的长者,因为长者德高望重,所以第i个星球的球长一定被第i位长者管辖且长者管辖自己。每一位长者手里有一份名单Bi,上面记录着一些长者的号。因为一些奥妙重重的原因,第i位长者的名单上只可能有1至i−1中的一些编号并且保证不会重复。因为长者都德高望重,所以第i位长者管辖第j位长者的充要条件是:对于每一个k属于Bi,第k位长者管辖第j位长者。

此时,有一位记者想对一些球长进行采访,为了保证采访顺利,他决定先与一些长者搞好关系,以便采访被这些长者管辖的球长。为了与更多的球长谈笑风生,这位记者会给你提出m个询问。第i个询问中记者会给你fi个长者的编号,你需要回答有多少个星球的球长至少直接或间接被一位长者管辖。

【输入】
第一行一个数n,表示星球的个数。

接下来n行,每一行描述一个Bi:首先给出Bi的大小szi(可能为0),接下来szi个数,描述Bi中的每一个元素。保证Bi中的数没有重复。

接下来一行,给出一个数m,表示询问的个数。

接下来m行,每一行描述一个询问:格式同上文对于集合Bi的格式。

【输出】
共m行,第i行输出第i次询问的答案。

【输入样例】

7
0
1 1
1 1
1 2
2 2 3
0
2 2 6
3
2 2 3
2 3 5
2 4 5

【输出样例】

3
3
4

这题描述很绕,简化一下就是长老i只能控制他\(B_i\)中所有人都控制了的长老,然后给你一堆长老,问你他们总共能控制多少个长老。

首先来从最简单的开始分析,当他\(|B_i|=0\)时,直接不管,他只能控制自己,然后当他\(|B_i|=1\)时,那他就只能控制那一个长老控制的长老,所以我们很容易想到从i到b连一条边,这样就能表示i能控制的b所控制的长老

关键就是\(|B_i|>1\)怎么办,这里可以直观理解,如果说我们有一个虚拟根,只能控制自己的长老连的这个根,从上面的推理可以发现其实这是一棵树,那我们直接找这些点的lca,不就是\(B_i\)所有点共同控制的点了吗?所以i就直接连lca就行了。

但是,这里有问题,就是在所有节点都是\(|B_i|≤1\)时,这个确实是树,但是你不能保证出现\(|B_i|>1\)的情况时还是一棵树啊。这里要归纳递推一下,首先,关于第一个\(|B_i|>1\)的点,根据上一段的推理,直接连到lca下,那么这个树形结构不会变化,所以,可以类推到第二个,第三个,对于任意一个点,树形结构都不会变,这样就能保证正确性了。

说多了,那么我们现在建完树了,我们发现这时,i号长老控制的就是从i到虚拟根的所有节点,问题就转换成了:给你一棵树,给你一些点,求这些点到根的路径上总共有多少个节点。

把所有的点按dfs序排序,先算第一个点到根的距离,在算第二个到根的距离,因为会有重复,所以减掉lca到根的距离,于是我们能得到一个公式:\(dep[u_0]+∑_{i=1}^{sz-1}{dep[u_i]-dep[lca(u_i,u_{i-1})]}\)

至于为什要按dfs序排序,这是个常用的技巧,你可以想我们其实是在从左往右处理这棵树,这样每次计算都会保证目前的节点的前面都已经算过了且不会再算一次,这样就可以避免重复。这其实是虚树,但这个知识点有点难我就这样简单的讲了。

lca直接用倍增维护,该题时间很极限,要卡一下常,但其实用一下快读和printf就差不多了

code:

#include <iostream>
#include <algorithm>
#include <cstring>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int N = 200001;
const int M = 200001;
const int INF = 0x3f3f3f3f3f3f3f3f;
const int LOGN = 17;

int read() {
    int k = 0, f = 1;char c = getchar();
    for (;c < '0' || c>'9';c = getchar())if (c == '-')f = -1;
    for (;c >= '0' && c <= '9';c = getchar())k = (k << 1) + (k << 3) + (c ^ 48);
    return k * f;
}

int n, m;
vector<int>G[N];
int dep[N], fa[N][LOGN + 1];
int dfn[N], idx = 1;

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

void dfs(int u) {
    dfn[u] = idx++;
    for (int v : G[u])dfs(v);
}

bool cmp(int& a, int& b) {
    return dfn[a] < dfn[b];
}

signed main() {
    n = read();
    for (int i = 1;i <= n;i++)dep[i] = 1;
    for (int i = 1;i <= n;i++) {
        int sz = read();
        if (sz == 1) {
            fa[i][0] = read();
        }
        else if(sz != 0 ){
            int d = read();sz--;
            while (sz--)d = lca(d, read());
            fa[i][0] = d;
        }
        G[fa[i][0]].push_back(i);
        dep[i] = dep[fa[i][0]] + 1;
        for (int j = 1;j <= LOGN;j++) {
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
        }
    }
    dfs(0);
    m = read();
    while (m--) {
        vector<int>temp;
        int siz = read();
        if (siz == 0) {
            printf("0\n");
            continue;
        }
        while (siz--)temp.push_back(read());
        sort(temp.begin(), temp.end(), cmp);
        int ans = dep[temp[0]];
        for (int i = 1;i < temp.size();i++) {
            ans += dep[temp[i]] - dep[lca(temp[i], temp[i - 1])];
        }
        printf("%lld\n", ans);
    }
}
posted @ 2026-09-12 16:26  DK_orz  阅读(9)  评论(0)    收藏  举报