洛谷题单指南-字符串-P4592 [TJOI2018] 异或

原题链接:https://www.luogu.com.cn/problem/P4592

题意解读:在一定范围内,查找一个值与z异或最大,依然是一个区间异或问题,直觉上可以使用持久化Trie。

解题思路:

设seq[i]表示节点i在dfs过程的顺序号siz[i]表示节点i为根的子树大小,depth[i]表示节点i所在的深度;

对于op = 1,在x的子树中查找与z异或最大值,

由于树在前序dfs过程中,x的子树所有节点顺序号一定是升序的,因此可以在dfs(先根)过程中,针对当前节点顺序号以及前一个顺序号建立Trie,记为trie1;对于x的子树范围,在顺序号中就是一段连续的内容,起点是seq[x],终点是seq[x] + siz[x] - 1。

对于op = 2,在x、y的路径中查找与z异或最大值,

可以借助于计算LCA(x,y),再分别在seq[x] ~ seq[LCA(x,y)],seq[y] ~ seq[LCA(x,y)]范围查找与z异或最大值,再求两者之间的最大值,因此可以在dfs过程中,针对当前节点顺序号以及父节点顺序号建立Trie,记为trie2;

可以看出,此题综合性非常强,需要以下前置知识作为基础:

1、邻接表建图建树

2、树的先序DFS,并记录节点顺序号

3、在DFS中,统计节点所在子树的大小

4、在DFS中,计算节点的深度,用于LCA

5、倍增法求LCA,以及在DFS中递推计算fa

6、在持久化Trie中添加节点

7、在持久化Trie中查找区间异或最大值

下面就结合代码,在注释中详细阐明以上内容。

100分代码:

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

const int N = 100005;
int n, q;
vector<int> tree[N]; //领接表
int w[N], seq[N], seqno, siz[N], depth[N]; //w:节点权值,seq:dfs前序编号,siz:子树大小,depth:深度
int root1[N], root2[N]; //记录trie树的根节点
int trie1[N * 32][2], trie2[N * 32][2], idx1, idx2, maxid1[N * 32], maxid2[N * 32]; //trie树以及经过每个节点最大的数的编号
int fa[N][20]; //fa[i][j]表示从i节点向上跳2^j所到达的节点

//建立trie1
void add1(int id)
{
    int sq = seq[id]; //获取顺序号
    root1[sq] = ++idx1; //建立根节点
    int cur = root1[sq]; //当前根节点
    int pre = root1[sq - 1]; //前一个根节点

    for(int i = 30; i >= 0; i--)
    {
        int v = w[id] >> i & 1;
        trie1[cur][!v] = trie1[pre][!v];
        trie1[cur][v] = ++idx1;
        maxid1[cur] = sq;
        cur = trie1[cur][v], pre = trie1[pre][v];
    }
    maxid1[cur] = sq;
}

//建立trie2
void add2(int id, int fid)
{
    int sq = seq[id], fsq = seq[fid]; //获取顺序号
    root2[sq] = ++idx2; //建立根节点
    int cur = root2[sq]; //当前根节点
    int pre = root2[fsq]; //前一个根节点

    for(int i = 30; i >= 0; i--)
    {
        int v = w[id] >> i & 1;
        trie2[cur][!v] = trie2[pre][!v];
        trie2[cur][v] = ++idx2;
        maxid2[cur] = sq;
        cur = trie2[cur][v], pre = trie2[pre][v];
    }
    maxid2[cur] = sq;
}

//在trie1中l~r范围内查找与val异或最大结果
int find1(int l, int r, int val)
{
    int res = 0;
    int u = root1[r]; //根,先在1~r范围查找
    for(int i = 30; i >= 0; i--)
    {
        int v = val >> i & 1;
        if(maxid1[trie1[u][!v]] >= l) //如果相反位的节点存在且最大编号>=l,限定范围l~r
        {
            u = trie1[u][!v];
            res = res * 2 + 1; //异或后1对结果的贡献
        }
        else
        {
            u = trie1[u][v];
            res = res * 2; //异或后0对结果的贡献
        }
    }
    return res;
}

//在trie2中l~r范围内查找与val异或最大结果
int find2(int l, int r, int val)
{
    int res = 0;
    int u = root2[r]; //根,先在1~r范围查找
    for(int i = 30; i >= 0; i--)
    {
        int v = val >> i & 1;
        if(maxid2[trie2[u][!v]] >= l) //如果相反位的节点存在且最大编号>=l,限定范围l~r
        {
            u = trie2[u][!v];
            res = res * 2 + 1; //异或后1对结果的贡献
        }
        else
        {
            u = trie2[u][v];
            res = res * 2; //异或后0对结果的贡献
        }
    }
    return res;
}

//从根u,父节点f开始dfs
void dfs(int u, int f)
{
    seq[u] = ++seqno; //遍历顺序号,前序
    add1(u); //以dfs前序关系建立trie1
    add2(u, f); //以父子关系建立trie2
    depth[u] = depth[f] + 1; //更新深度
    siz[u] = 1; //u为根的子树初始只有他自己
    fa[u][0] = f; //从u往上跳2^0步到f
    for(int i = 1; i < 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; //递推计算fa
    for(int v : tree[u])
    {
        if(v == f) continue; //避免往回走
        dfs(v, u);
        siz[u] += siz[v]; //u为根的子树大小是所有子树的大小之和
    }
}

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

int main()
{
    cin >> n >> q;
    for(int i = 1; i <= n; i++) cin >> w[i]; //读取权值
    for(int i = 1; i < n; i++) //双向建图
    {
        int u, v;
        cin >> u >> v;
        tree[u].push_back(v);
        tree[v].push_back(u);
    }
    dfs(1, 0);
    while(q--)
    {
        int op, x, y, z;
        cin >> op;
        if(op == 1)
        {
            cin >> x >> z;
            cout << find1(seq[x], seq[x] + siz[x] - 1, z) << endl;
        }
        else
        {
            cin >> x >> y >> z;
            int xy = lca(x, y);
            cout << max(find2(seq[xy], seq[x], z), find2(seq[xy], seq[y], z)) << endl;
        }
    }
}

 

posted @ 2024-10-29 14:27  五月江城  阅读(28)  评论(0编辑  收藏  举报