• 可持久化数据结构——最大异或和(trie)+第K小数(线段树)


    可持久化数据结构:可以存下数据结构的所有历史版本:

    核心思想:只记录和前一个版本不一样的节点。

    可持久化trie数构造过程:

    与Trie的节点一样,可持久化trie的每一个节点也有若干字符指针指向子节点,可以用trie[x,c]保存节点x的字符指针C指向的子节点的编号。插入新的字符串操作如下、

    1.设p等于当前trie的根节点root,i=0;

    2.建立一个新的节点q=++idx

    3.检查p是否为空,为空的话则不用对q做额外操作,不为空的话应该先将p上的所有信息先复制到q节点下面,也就是让q指向p的所有子节点。也就是对于每一种字符C,令trie[q,c]=trie[p,c].

    4.为新的要插入字符si建立一个新编号q`=++idx,令trie[q,si]=q`。

    5.令p=trie[p,si],(这里p下次会用于上面的检查是否为空),q=trie[q,si], i=i+1(指向下一个要插入的字符).

    6.然后重复步骤3~5.

    图示:略

    传送门:256. 最大异或和 - AcWing题库
    思路:

    本题需要将数字转化成二进制01串进行插入,

    针对题目中的查询操作,建立一个新的前缀和数组S[i]表示前i个数字异或的和=A1^A2^...Ai.

    对于询问操作里面Ap^...AN^ x 转换成到前缀和数组就是Sp-1^SN^ x,询问就是变成了在l~r之间选择一个p使得Sp-1^SN^ x最大,其中SN^ x是定值。

    可持久化的trie可以查询历史版本的trie,

    对于区间[l,r]只要查询root[r]的字典树就可以得到1~r(解决r)里面可以使得Sp-1^SN^ x最大的p值。

    对于左边界l,可以在trie树的每一个节点上面记录一个max_id为当前子树中下标最大的值,如果标记大于l则可以进入左子树进行查找(解决l)。
    代码:

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. typedef long long ll;
    6. const int N=6e5+10,M=N*25;
    7. int n,m;
    8. int s[N];
    9. int tr[M][2];
    10. int max_id[M],idx;
    11. int root[N];
    12. void insert(int i,int k,int p,int q)
    13. {
    14. if(k<0)
    15. {
    16. max_id[q]=i; //当前这个数字的编号。
    17. return;
    18. }
    19. int v=s[i]>>k&1;
    20. if(p) tr[q][!v]=tr[p][!v];//把上一版本的p的其余节点都复制到q节点下来
    21. tr[q][v]=++idx; //然后另外开一个节点存要插入的
    22. insert(i,k-1,tr[p][v],tr[q][v]); //
    23. max_id[q]=max(max_id[tr[q][0]],max_id[tr[q][1]]); //pushup操作
    24. }
    25. int query(int root,int c,int l) //查询root版本下面与C异或和最大的一个前缀和s[p-1],其中限制为l
    26. {
    27. int p=root;
    28. for(int i=23;i>=0;i--)
    29. {
    30. int v=c>>i&1;
    31. if(max_id[tr[p][v^1]]>=l)//如果另外一边存在并且没有超过边界的话。
    32. p=tr[p][v^1];
    33. else p=tr[p][v]; //否则都从相同的边下去
    34. }
    35. return c^s[max_id[p]]; //返回最大异或和
    36. }
    37. int main()
    38. {
    39. scanf("%d%d",&n,&m);
    40. max_id[0]=-1;
    41. root[0]=++idx;
    42. insert(0,23,0,root[0]); //因为后面要用到s[0]的值,所以s[0]也要作为一个值插进去
    43. for(int i=1;i<=n;i++)
    44. {
    45. int x;
    46. scanf("%d",&x);
    47. s[i]=s[i-1]^x;
    48. root[i]=++idx;
    49. insert(i,23,root[i-1],root[i]);
    50. }
    51. char op[2];
    52. int l,r,x;
    53. while(m--)
    54. {
    55. scanf("%s",op);
    56. if(*op=='A')
    57. {
    58. scanf("%d",&x);
    59. n++;
    60. s[n]=s[n-1]^x;
    61. root[n]=++idx;
    62. insert(n,23,root[n-1],root[n]);
    63. }else
    64. {
    65. scanf("%d%d%d",&l,&r,&x);
    66. printf("%d\n",query(root[r-1],s[n]^x,l-1));
    67. }
    68. }
    69. return 0;
    70. }

    线段树的可持久化操作:

    首先,线段树的可持久化的结构其实跟trie的结构是差不多的。

    然后可持久化线段树里面存的l,r不再是下标,而是数值区间。

    传送门:255. 第K小数 - AcWing题库

    思路:原题的数值范围很大,但是区间范围却很小,所以这里需要对数据做离散化。

    和可持久化trie树一样用一个root[i]表示第i个版本的线段树,线段树里面会存一个cnt表示前p个元素里面在tr[p].l到tr[p].r之间的元素的数量有多少个,要求[l,r]的原区间里面第k小的元素要用第r个版本的线段树的cnt减去第l-1个版本的cnt,这个结果才是数组区间[l,r]里面的元素统计的数量。

    未完待续....

    代码:

    1. #include
    2. #include
    3. #include
    4. #include
    5. using namespace std;
    6. typedef long long ll;
    7. const int N=1e5+10,M=1e4+10;
    8. int n,m;
    9. int a[N];
    10. vector<int>nums;
    11. struct node
    12. {
    13. int l,r;
    14. int cnt; //表示在l,r之间的元素的个数
    15. }tr[N*4+N*17];
    16. int idx,root[N];
    17. int find(int x) //获取离散化之后的元素下标?
    18. {
    19. return lower_bound(nums.begin(),nums.end(),x)-nums.begin();
    20. }
    21. int build(int l,int r)
    22. {
    23. int p=++idx;
    24. if(l==r) return p;
    25. int mid=l+r>>1;
    26. tr[p].l=build(l,mid);
    27. tr[p].r=build(mid+1,r);
    28. return p;
    29. }
    30. int insert(int p,int l,int r,int x)
    31. {
    32. int q=++idx;
    33. tr[q]=tr[p];
    34. if(l==r)
    35. {
    36. tr[q].cnt++;
    37. return q;
    38. }
    39. int mid=l+r>>1;
    40. if(x<=mid) tr[q].l=insert(tr[p].l,l,mid,x);
    41. else tr[q].r=insert(tr[p].r,mid+1,r,x);
    42. tr[q].cnt=tr[tr[q].l].cnt+tr[tr[q].r].cnt;
    43. return q;
    44. }
    45. int query(int q,int p,int l,int r,int k) //
    46. {
    47. if(l==r) return r;
    48. int cnt=tr[tr[q].l].cnt-tr[tr[p].l].cnt;
    49. int mid=l+r>>1;
    50. if(k<=cnt)return query(tr[q].l,tr[p].l,l,mid,k);
    51. else return query(tr[q].r,tr[p].r,mid+1,r,k-cnt);
    52. }
    53. int main()
    54. {
    55. scanf("%d%d",&n,&m);
    56. for(int i=1;i<=n;i++)
    57. {
    58. scanf("%d",&a[i]);
    59. nums.push_back(a[i]);
    60. }
    61. sort(nums.begin(),nums.end());
    62. nums.erase(unique(nums.begin(),nums.end()),nums.end());
    63. root[0]=build(0,nums.size()-1);
    64. for(int i=1;i<=n;i++)
    65. {
    66. root[i]=insert(root[i-1],0,nums.size()-1,find(a[i]));
    67. }
    68. while(m--)
    69. {
    70. int l,r,k;
    71. scanf("%d%d%d",&l,&r,&k);
    72. printf("%d\n",nums[query(root[r],root[l-1],0,nums.size()-1,k)]);
    73. }
    74. return 0;
    75. }

  • 相关阅读:
    【MySQL从入门到精通】【高级篇】(二)MySQL目录结构与表在文件系统中的表示
    本地部署Jellyfin影音服务器并实现远程访问影音库
    使用MySQL如何查询一年中每月的记录数
    设计模式概述
    cannot find defineEmits(or defineProps) in ts的原因
    vue3 electron 打包后进程通信无效,开发环境正常
    【zedboard找不到COM串口bug】驱动下载地址
    (附源码)计算机毕业设计ssm《Python程序设计》教辅系统
    iOS应用程序数据保护:如何保护iOS应用程序中的图片、资源和敏感数据
    UGUI图集的理解与使用
  • 原文地址:https://blog.csdn.net/m0_62327332/article/details/127424417