【BZOJ4103】异或运算(THUSC2015)-可持久化trie树+位运算

测试地址:异或运算
题目大意:给定两个数列XY,分别包含NM个非负整数,其中N1000M300000,有P个询问,P500,每个询问给出5个参数u,d,l,r,k,意为求所有满足uid,ljr的数XixorYj中第k大的数。
做法:因为要去参加THUSC2017了,所以就想来做一下历年的题,结果……看到题目就蒙圈了,什么鬼?看了题解才知道是还没有学习的东西,于是紧赶慢赶一个小时把这个新东西学了。
这题的正确做法是可持久化trie树,也就是可持久化字典树。
初看这题,如果暴力求出所有异或出来的值,然后整体二分的话,总复杂度应为O(NMlog(NM)),显然是不行的。
注意到P不大,那么我们就不要拘泥于离线算法,而去寻求一个在线算法。我们可以基于以下步骤寻找第k大数:从高到低枚举每一个二进制位,对于一个二进制位,通过统计确定这个二进制位是填0还是填1,具体来说就是,如果这个位置上为1,合法的数字数量k,那么这个位置就应该填1,否则就填0。可以看出这很像是splay中查找第k大数的方法,只不过是基于二进制位查找,那么我们实际上要解决的就是统计上文中“合法的数字”的数量。注意到每一个二进制数可以表示成一个01串,那么我们可以把问题转化成求拥有某个前缀的字符串数量,很容易看出这个问题用一棵trie树就可以解决。
然而还要考虑子矩阵的限制。发现N比较小,所以我们枚举这一维,然后我们要求某个区间内拥有某个前缀的字符串数量,我们就可以仿造可持久化线段树的建树方式建造一棵可持久化trie树,使其满足一种前缀和的性质,这样的话每一次查询的复杂度就是O(31)
再来理一下整个解题的过程:用O(31M)的复杂度以数列Y建一棵可持久化trie树,然后再处理询问,最外层枚举二进制位,然后用一些指针来实现可持久化trie树上的查找,那么处理询问的总复杂度为O(31PN),整道题就这样解决了。具体可以看本人的代码。
犯二的地方:空间复杂度算错导致开小了数组,RE了两次,下次要注意…
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,q,rt[300010]={0},sum[9600010]={0},ch[9600010][2]={0},tot=0;
int nowl[1010],nowr[1010],bt[32];
bool x[1010][32]={0};

void insert(int last,int &no,int v,int d)
{
  no=++tot;
  sum[no]=sum[last],ch[no][0]=ch[last][0],ch[no][1]=ch[last][1];
  if (d<0) {sum[no]++;return;}
  if (v&bt[d]) insert(ch[last][1],ch[no][1],v,d-1);
  else insert(ch[last][0],ch[no][0],v,d-1);
  sum[no]=sum[ch[no][0]]+sum[ch[no][1]];
}

int query(int u,int d,int l,int r,int k)
{
  for(int i=u;i<=d;i++)
    nowl[i]=rt[l-1],nowr[i]=rt[r];
  int ans=0,s;
  for(int j=30;j>=0;j--)
  {
    s=0;
    for(int i=u;i<=d;i++)
      s+=sum[ch[nowr[i]][!x[i][j]]]-sum[ch[nowl[i]][!x[i][j]]];
    if (s>=k)
    {
      for(int i=u;i<=d;i++)
      {
        nowl[i]=ch[nowl[i]][!x[i][j]];
        nowr[i]=ch[nowr[i]][!x[i][j]];
      }
      ans+=bt[j];
    }
    else
    {
      for(int i=u;i<=d;i++)
      {
        nowl[i]=ch[nowl[i]][x[i][j]];
        nowr[i]=ch[nowr[i]][x[i][j]];
      }
      k-=s;
    }
  }
  return ans;
}

int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)
  {
    int a,j=0;
    scanf("%d",&a);
    while(a)
    {
      x[i][j]=a&1;
      a>>=1;j++;
    }
  }
  bt[0]=1;
  for(int i=1;i<=30;i++) bt[i]=bt[i-1]<<1;
  for(int i=1;i<=m;i++)
  {
    int a;
    scanf("%d",&a);
    insert(rt[i-1],rt[i],a,30);
  }

  scanf("%d",&q);
  for(int i=1;i<=q;i++)
  {
    int u,d,l,r,k;
    scanf("%d%d%d%d%d",&u,&d,&l,&r,&k);
    printf("%d\n",query(u,d,l,r,k));
  }

  return 0;
}
posted @ 2017-05-18 19:35  Maxwei_wzj  阅读(115)  评论(0编辑  收藏  举报