bzoj4184shallot

题意

给出一个初始为空的数字集合,每次添加一个数字/删除一个存在的数字,然后输出选出一些数进行异或能够得到的最大数值.操作次数<=500000,数字大小<2^31

分析

看上去我们只要写一个支持插入删除的线性基就可以了,然而线性基不支持删除,因此我们需要把删除操作也转化为插入操作.解决的方法是在时间轴上考虑问题.
我们需要求解的是在时刻1,时刻2,时刻3....时刻n时的线性基,那么就相当于要知道这些时刻的数字集合.如果某个数字在时刻s被插入,时刻t之后被删除,那么相当于时刻s到时刻t的数字集合中都加入了这个数,也就是区间[s,t]中的每个集合都加入了这个数字.每个数字在哪些区间中加入可以在读入的时候用个map求出来.
于是问题转化为:有n个数字集合顺序排放,进行多次"往某一段区间[l,r]中每个集合都加入一个数x"的操作,最后查询每个数字集合的线性基.
可能是因为我比较蠢所以感觉这题的标算非常的妙
一开始,我没有利用最后查询的条件,想着直接把懒标记线段树套个线性基,每个线段树节点上挂着一个大小为32的数组存储所有能覆盖这段区间的数字的线性基,最后把所有标记暴力下传到叶节点,修改的时候动态打标记.这样做修改的总复杂度是O(nlogn*32),但是最后暴力下传标记的复杂度可以达到O(nlogn*32*32),绝对会T.而且内存也是不够用的.
然后看题解,发现标算同样是在时间轴上考虑问题,但是打标记的方法很高明.虽然对于求线性基的问题,我们有时需要在线段树一个节点上存储一些数字的线性基,这样会优化时间复杂度(例如SCOI2016幸运数字),但是有的时候,把线性基求出来是不必要的,在时间效率上是得不偿失的.在这道题中,我们只在所有修改都完成后才真正需要查询线性基.在进行每一次修改的时候都维护线性基是不必要的,并且存储了大量的冗余信息,把"l到r添加一个数字x"的操作拆成了一堆碎片,散布于某些节点的线性基中.造成的结果,就是最后不得不用O(nlogn*32*32)的复杂度暴力下传标记.
如果我们在线段树的标记里存储的信息不是线性基,而是"在这个节点中插入哪些数字",最后的答案统计的时间复杂度就变得清真起来.
具体做法是,我们在线段树每个节点开一个vector,对于区间加入一个数字的操作,我们往区间覆盖的每个线段树节点的vector里把这个数字加进去,每个数字加到最多O(logn)个节点中所以这样的空间复杂度是nlogn的.显然,最后某个位置的答案就是它对应的叶节点到线段树根节点路径上所有的vector里的数字的线性基.那么自然我们可以从线段树的根节点出发,求出根节点到每个节点路径上的数字的线性基BASE[x],某个节点的BASE[]可以由它的父节点的BASE[]中插入这个节点vector的数字得到.如果直接每个节点开大小为32的数组,还是会炸.其实我们可以dfs,保存当前的一条链即可.细节见代码.

#include<cstdio>
#include<vector>
#include<map>
using namespace std;
const int maxn=500005;
struct Base{
  int b[31];
  int val(){
    int ans=0;
    for(int i=0;i<31;++i)ans^=b[i];
    return ans;
  }
  void insert(int x){
    for(int i=30;i>=0;--i){
      if(x>>i&1){
	    if(b[i])x^=b[i];
	    else{
	      b[i]=x;
	      for(int k=i-1;k>=0;--k)if(b[i]>>k&1)b[i]^=b[k];
	      for(int k=i+1;k<31;++k)if(b[k]>>i&1)b[k]^=b[i];
	      break;
	    }
      }
    }
  }
}B[32];
vector<int> a[maxn<<2];
void add(int rt,int l,int r,int ql,int qr,int x){//if(rt==1)printf("%d %d %d\n",ql,qr,x);
  if(ql<=l&&r<=qr){
    a[rt].push_back(x);return;
  }
  int mid=(l+r)>>1;
  if(ql<=mid)add(rt<<1,l,mid,ql,qr,x);
  if(qr>mid) add(rt<<1|1,mid+1,r,ql,qr,x);
}
void query(int rt,int l,int r,int dep){
  for(vector<int>::iterator pt=a[rt].begin();pt!=a[rt].end();++pt){
    B[dep].insert(*pt);
  }
  if(l==r)printf("%d\n",B[dep].val());
  else{
    int mid=(l+r)>>1;
    B[dep+1]=B[dep];
    query(rt<<1,l,mid,dep+1);
    B[dep+1]=B[dep];
    query(rt<<1|1,mid+1,r,dep+1);
  }
}
int main(){
  int n;scanf("%d",&n);
  int x;
  map<int,int> dict;
  map<int,int> last;
  for(int i=1;i<=n;++i){
    scanf("%d",&x);
    if(x<0){
      dict[-x]--;
      if(dict[-x]==0){
	    add(1,1,n,last[-x],i-1,-x);
      }
    }else{
      dict[x]++;
      if(dict[x]==1)last[x]=i;
    }
  }
  for(map<int,int>::iterator pt=dict.begin();pt!=dict.end();++pt){
    if(pt->second!=0){
      add(1,1,n,last[pt->first],n,pt->first);
    }
  }
  query(1,1,n,0);
  return 0;
}

posted @ 2017-04-13 19:23  liu_runda  阅读(378)  评论(0编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难