A、Ancestor题解

前置知识:dfs序,倍增法求lca

OI wiki上的倍增LCA模板, 看文字嫌累还有b站的讲解视频(个人觉得不错)

题目主要是让我们求长度为k-1的点集的最近公共祖先(一个点被删了),对于求多个点的最近公共祖先,我们并不真的要对所有点两两都求一次。我们只用取这些点中dfs序最小和最大的两个点来求最近公共祖先就行。 证明可以看看去如何在 DAG 中找多个点的 LCA ? 看,这里不多做补充。

问题解析

知道了这两个前置知识后,这题就变的很简单了。

  • 我们可以先对所有的点来一遍前序遍历,求得他们的dfs序。

  • 根据dfs序对k集合的点进行升序排序(两个树的dfs序不一定一样的,所以我们两边都要求嗷,排序也是)。

  • 然后我们枚举第1~第k个点作为被删除的点,对于这两个树,我们取他们k集合中dfs序最大和最小的两个点来分别求lca。如果它们中有点正好是这次被删除的点,那我们就取第二大(第二小)的点(哪个被删改哪个,没被删就不理)。

  • 之后比较两边求出来的点,看A树的祖先的权值是否大于B树的祖先,如果大于,则计数器++。

样例解释

输入:

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

输出:
1

黑色是点的编号,红色是每个点的权值,蓝色是他们的dfs序 alt

  • 树A的k集合排序后为:3 4 5.

  • 树B的k集合排序后为:5 3 4.

以A的k集合为准,我们枚举删除的点:

  • 删除点3——A的最大dfs序点为5,最小为4;B的最大点为4,最小为5

    求得A的祖先为点4,权值为4,B为1,权值为7,不满足,cnt=0;

  • 删除点4——A的最大dfs序点为5,最小为3;B的最大点为3,最小为5

    求得A的祖先为点2,权值为6,B为1,权值为7,不满足,cnt=0;

  • 删除点5——A的最大dfs序点为4,最小为3;B的最大点为4,最小为3

    求得A的祖先为点2,权值为6,B为3,权值为4,满足,cnt=1;

复杂度分析

倍增lca预处理复杂度为:n*logn.

对k集合点排序:k*logk.

处理单次询问:logn.

AC代码

(代码很丑很长,主要是学艺不精重复的地方有点多不然可以很简洁的,但重要的是思路)

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>
#include<math.h>
#include<set>
#include<numeric>
#include<string>
#include<string.h>
#include<iterator>
#include<queue>

#define endl '\n'
#define int ll
#define PI acos(-1)
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll, ll>PII;
const int N = 2e5 + 50, MOD = 1e9 + 7;

//deep数组记录的是树上每个节点的深度,fa数组记录的是每个点的父节点,这两个是倍增lca需要的
int deepA[N], faA[N][31], deepB[N], faB[N][31];

//st数组记录每个点的dfs序,w数组记录每个点的权值
int stA[N], stB[N], wA[N], wB[N];

//树数组
vector<int>treeA[N], treeB[N];

bool cmpA(int a, int b)
{
    return stA[a] < stA[b];
}

bool cmpB(int a, int b)
{
    return stB[a] < stB[b];
}

// dfs,用来为 lca 算法做准备。接受两个参数:dfs 起始节点和它的父亲节点。
void dfsA(int root, int fno, int& cnt) {
    // 初始化:第 2^0 = 1 个祖先就是它的父亲节点,dep 也比父亲节点多 1。
    faA[root][0] = fno;
    //记录当前点的dfs序
    stA[root] = cnt++;
    deepA[root] = deepA[faA[root][0]] + 1;
    // 初始化:其他的祖先节点:第 2^i 的祖先节点是第 2^(i-1) 的祖先节点的第
    // 2^(i-1) 的祖先节点。
    for (int i = 1; i < 31; ++i) {
        faA[root][i] = faA[faA[root][i - 1]][i - 1];
    }
    // 遍历子节点来进行 dfs。
    int sz = treeA[root].size();
    for (int i = 0; i < sz; ++i) {
        if (treeA[root][i] == fno) continue;
        dfsA(treeA[root][i], root, cnt);
    }
}

void dfsB(int root, int fno, int& cnt) {
    faB[root][0] = fno;
    stB[root] = cnt++;
    deepB[root] = deepB[faB[root][0]] + 1;
    for (int i = 1; i < 31; ++i) {
        faB[root][i] = faB[faB[root][i - 1]][i - 1];
    }
    int sz = treeB[root].size();
    for (int i = 0; i < sz; ++i) {
        if (treeB[root][i] == fno) continue;
        dfsB(treeB[root][i], root, cnt);
    }
}

// lca。用倍增算法算取 x 和 y 的 lca 节点。
int lcaA(int x, int y) {
    // 令 y 比 x 深。
    if (deepA[x] > deepA[y]) swap(x, y);
    // 令 y 和 x 在一个深度。
    int tmp = deepA[y] - deepA[x], ans = 0;
    for (int j = 0; tmp; ++j, tmp >>= 1)
        if (tmp & 1) y = faA[y][j];
    // 如果这个时候 y = x,那么 x,y 就都是它们自己的祖先。
    if (y == x) return x;
    // 不然的话,找到第一个不是它们祖先的两个点。
    for (int j = 30; j >= 0 && y != x; --j) {
        if (faA[x][j] != faA[y][j]) {
            x = faA[x][j];
            y = faA[y][j];
        }
    }
    // 返回结果。
    return faA[x][0];
}

int lcaB(int x, int y) {
    if (deepB[x] > deepB[y]) swap(x, y);
    int tmp = deepB[y] - deepB[x], ans = 0;
    for (int j = 0; tmp; ++j, tmp >>= 1)
        if (tmp & 1) y = faB[y][j];
    if (y == x) return x;
    for (int j = 30; j >= 0 && y != x; --j) {
        if (faB[x][j] != faB[y][j]) {
            x = faB[x][j];
            y = faB[y][j];
        }
    }
    return faB[x][0];
}

void solve()
{
    int n, m, x;
    cin >> n >> m;
    vector<int>ansA(m), ansB(m);
    for (int i = 0; i < m; i++)cin >> ansA[i], ansB[i] = ansA[i];
    for (int i = 1; i <= n; i++)cin >> wA[i];
    for (int i = 2; i <= n; i++)
    {
        cin >> x;
        treeA[x].push_back(i);
    }
    int cnt = 1;
    //预处理lca,并且求得每个点的dfs序
    dfsA(1, 0, cnt);
    for (int i = 1; i <= n; i++)cin >> wB[i];
    for (int i = 2; i <= n; i++)
    {
        cin >> x;
        treeB[x].push_back(i);
    }
    cnt = 1;
    dfsB(1, 0, cnt);
    //按照dfs序对k集合的点进行升序排序
    sort(ansA.begin(), ansA.end(), cmpA);
    sort(ansB.begin(), ansB.end(), cmpB);
    cnt = 0;
    
    //我们以A树的k集合为标准枚举所有的点
    for (int i = 0; i < m; i++)
    {
        //x记录k集合中dfs序最小的点,y记录最大的点
        int xA = ansA[0], yA = ansA[m - 1], xB = ansB[0], yB = ansB[m - 1];
        //如果最大的点被删除,我们取第二大的
        if (i == 0)xA = ansA[1];
        //如果最小的点被删除,我们取第二小的
        else if (i == m - 1)yA = ansA[m - 2];

        if (xB == ansA[i])xB = ansB[1];
        else if (yB == ansA[i])yB = ansB[m - 2];

        //求的两个树的最近公共祖先
        int xlca = lcaA(xA, yA), ylca = lcaB(xB, yB);
        if (wA[xlca] > wB[ylca])cnt++;
    }
    cout << cnt << endl;
}

signed main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int t = 1;
    //cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}