首页 > 其他分享 >洛谷题单指南-字符串-P4735 最大异或和

洛谷题单指南-字符串-P4735 最大异或和

时间:2024-10-21 12:31:33浏览次数:5  
标签:maxid int 洛谷题 节点 trie 异或 P4735 id

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

题意解读:已知长度为n的数组a[],要在l~r范围找到一个p,使得a[p]^a[p+1]^...^a[n]^x最大,求这个最大的异或值。

解题思路:

1、利用前缀和将问题转化

设s[]是a[]的前缀异或数组,要计算a中一段范围l~r的异或,可以借助于s

由于s[r] = a[0]^a[1]^...a[l-1]^a[l]^...^a[r],s[l-1] = a[0]^a[1]^...^a[l-1]

因此s[r] ^ s[l-1] = a[l]^a[l+1]^...^a[r]

对于a[p]^a[p+1]^...^a[n]^x,可以转化为s[n]^x^s[p-1],即问题转化为在s[l-1]~s[r-1]范围内找到一个s[p-1]使得s[n]^x与其异或值最大。

要计算与一个数异或最大的值,可以借助于Trie树,但是这里限制条件是要在一个范围内找到与某个数异或的最大值,需要引入一种新的数据结构-持久化Trie树。

2、持久化Trie树介绍

所谓持久化Trie,一般用于01Trie,是指对每一个数字进行加入trie操作时,都建立一个新的根节点,从该根节点可以查找到之前添加的所有的数,具体如何实现呢?我们以2 6 4 3 为例进行Trie添加操作:

添加的过程有以下特点:

  • 每添加一个数s[i],都建立一个根节点root[i],从root[i]可以查找到s[1]~s[i]所有数
  • 当添加s[i]时,对于s[i]的每一个二进制位都新建立节点,对于不在s[i]中的二进制位,复制前一个根的节点
  • 如添加6时,trie[6][0]=trie[1][0]是复制前一个根节点,而trie[6][1]=7是新建节点

查找的过程:

要在L~R范围内查找与x异或最大的结果,可以在根为root[R]中去找1~R范围的数,同时用一个maxid[]来记录Trie树中每一个节点所经过的数的最大id,这样在查找最大异或值时,只需要判断相反位节点存在且maxid[]>=L即说明是L~R范围的数,不存在则走相同位的节点。

Trie树操作核心代码:

//将s[id]添加到持久化Trie
void add(int id)
{
    root[id] = ++idx; //创建当前根节点
    int cur = root[id]; //当前根
    int pre = root[id - 1]; //前一个根
    for(int i = 23; i >= 0; i--)
    {
        int v = s[id] >> i & 1;
        trie[cur][!v] = trie[pre][!v]; //相反位复制上一个根
        trie[cur][v] = ++idx; //对s[i]每个二进制位新建节点
        maxid[cur] = id; //更新maxid
        cur = trie[cur][v], pre = trie[pre][v]; //根沿着s[i]的二进制位往下走
    }
    maxid[cur] = id; //更新maxid
}

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

3、最终问题求解

第一步:计算前缀异或

第二步:建立持久化Trie

第三步:范围查找最大异或值

100分代码:

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

const int N = 600005, M = N * 24;
int s[N]; //前缀异或数组
int root[N]; //所有版本trie的根节点
int trie[M][2], idx, maxid[M]; //trie树,maxid[]保存经过某个节点的数的最大s编号
int n, m;
char op;
int l, r, x;

//将s[id]添加到持久化Trie
void add(int id)
{
    root[id] = ++idx; //创建当前根节点
    int cur = root[id]; //当前根
    int pre = root[id - 1]; //前一个根
    for(int i = 23; i >= 0; i--)
    {
        int v = s[id] >> i & 1;
        trie[cur][!v] = trie[pre][!v]; //相反位复制上一个根
        trie[cur][v] = ++idx; //对s[i]每个二进制位新建节点
        maxid[cur] = id; //更新maxid
        cur = trie[cur][v], pre = trie[pre][v]; //根沿着s[i]的二进制位往下走
    }
    maxid[cur] = id; //更新maxid
}

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

int main()
{
    cin >> n >> m;
    maxid[0] = -1; //不存在的节点maxid=-1,便于find中比较
    add(0); //第一个数添加0,使得便于区间计算
    for(int i = 1; i <= n; i++)
    {
        cin >> s[i];
        s[i] = s[i - 1] ^ s[i]; //计算前缀异或
        add(i); //添加到持久化Trie
    } 
    while(m--)
    {
        cin >> op;
        if(op == 'A')
        {
            cin >> x;
            s[n + 1] = s[n] ^ x;
            n++;
            add(n);
        }
        else
        {
            cin >> l >> r >> x;
            cout << find(l - 1, r - 1, x ^ s[n]) << endl;
        }
    }
}

 

标签:maxid,int,洛谷题,节点,trie,异或,P4735,id
From: https://www.cnblogs.com/jcwy/p/18472216

相关文章

  • B. 异或(xor)
    B.异或(xor)题意给你\(n\)个数,你可以执行若干次操作,每次选择一个区间异或上一个数,问最少操作次数使得所有数变成\(0\),输出最小的操作次数。\(n\le17,a_i\le10^8\)solution居然还有异或差分这种思想,是我孤陋寡闻了因为是区间操作,所以我们考虑在异或差分序列上操作。显......
  • 洛谷题单指南-字符串-P2922 [USACO08DEC] Secret Message G
    原题链接:https://www.luogu.com.cn/problem/P2922题意解读:已知M个01串,给出N个01串,对于N个串的每一个,求在M个串中有多少与其有公共前缀,且前缀长度是两个串中较小者。解题思路:用Trie树存储M个01串,用cnt1[]记录某个节点结束的01串个数,cnt2[]记录经过某个节点的01串的数量对于N个0......
  • 洛谷题单指南-字符串-P2375 [NOI2014] 动物园
    原题链接:https://www.luogu.com.cn/problem/P2375题意解读:计算字符串所有子串的不重叠相同前后缀数量。解题思路:1、KMP+暴力通过Next数组,可以计算所有子串相同前后缀的数量然后枚举Next数组,通过回跳Next[j]、Next[Next[j]-1]、Next[Next[Next[j]-1]-1]......来统计长度小于......
  • 洛谷题单指南-字符串-P3435 [POI2006] OKR-Periods of Words
    原题链接:https://www.luogu.com.cn/problem/P3435题意解读:定义字符串a是b的周期,当a是b的真前缀,且b是aa的前缀。给定字符串s,求s每一个前缀的最大周期长度之和。解题思路:针对字符串babababa进行样例模拟:前缀子串  最大周期  周期长度b空0ba空0babba2......
  • 洛谷题单指南-字符串-Test
    原题链接:https://www.luogu.com.cn/problem/CF25E https://codeforces.com/contest/25/problem/E题意解读:给定a,b,c三个字符串,求包含a、b、c的最短字符串长度。解题思路:要得到包含a、b、c的字符串,可以通过a、b、c连接形成,而要使得连接后的字符串最短,可以尽可能的利用重叠部分......
  • 洛谷题单指南-字符串-P1470 [USACO2.3] 最长前缀 Longest Prefix
    原题链接:https://www.luogu.com.cn/problem/P1470题意解读:求s最长前缀长度,使得可以拆解成p集合中的字符串解题思路:动态规划:设s字符串下标从1开始,p集合用set<string>保存所有的元素状态表示:设f[i]表示前i个字符s[0~i-1]是否能拆解成p中的元素状态计算:对于j=i-1开始往后倒......
  • 【题解】P3917 异或序列
    传送门也算是一个有关于异或的小trick吧,简单记录一下。可以维护原序列的前缀异或和\(sum\),于是原题答案贡献变为\(\sum\limits_{i=1}^n\sum\limits_{j=i}^nsum_j\oplussum_{i-1}\)。变形一下为\(\sum\limits_{i=0}^{n-1}\sum\limits_{j=1}^{i+1}sum_i\oplussum_{j}......
  • 洛谷题单指南-字符串-P5283 [十二省联考 2019] 异或粽子
    原题链接:https://www.luogu.com.cn/problem/P5283题意解读:n个整数,每次从从取l~r的数进行异或得到美味值,一共取k次,并计算这k个美味值之和的最大值。解题思路:1、如何O(1)的计算l~r数的异或,得到美味值可以借助前缀和思想,a[i]为第i个数,s[i]表示a[1]~a[i]每个数的异或值,要计算l~r的......
  • 洛谷题单指南-字符串-P2580 于是他错误的点名开始了
    原题链接:https://www.luogu.com.cn/problem/P2580题意解读:给n个字符串,再依次处理m个字符串,对于每个字符串,如果在前面n个字符串中输出OK,如果不在n个字符串中输出WRONG,如果在n个字符串中且不止一次查询过输出REPEAT。解题思路:1、set/map方法很简单直接,用set存下前n个字符串,map......
  • 洛谷题单指南-字符串-P1481 魔族密码
    原题链接:https://www.luogu.com.cn/problem/P1481题意解读:在n个字符串中找到最长的词链长度,定义字符串a、b、c可以形成词链,即a是b的前缀、b是c的前缀。解题思路:1、Trie树定义Trie树,也称前缀树、字典树,核心思想是将字符串拆解为单个字符,每个字符是树的一条边,节点是字符添加到树......