题意

给出一个初始为空的数字集合,每次添加一个数字/删除一个存在的数字,然后输出选出一些数进行异或能够得到的最大数值.操作次数<=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;
}

bzoj4184shallot的更多相关文章

随机推荐

  1. 基于Opencv的人脸检测及识别

    一.实验目的:我这里完成的是,将8张人脸图片(4组,每组两张)存入库中,选取1张图片,程序识别出与其匹配的另一张. 这里介绍分三个步骤完成该工作,①程序读取摄像头.拍照 ②程序从电脑文档中读取图片   ...

  2. [POJ2104]Kth Number-[整体二分]

    Description 传送门 Solution 将所有询问放在一起,二分答案的同时把区间[l,r]内的数按大小分类. Code #include<iostream> #include&l ...

  3. 【LG4631】[APIO2018]Circle selection 选圆圈

    [LG4631][APIO2018]Circle selection 选圆圈 题面 洛谷 题解 用\(kdt\)乱搞剪枝. 维护每个圆在\(x.y\)轴的坐标范围 相当于维护一个矩形的坐标范围为\([ ...

  4. Hadoop NameNode HA 和 ResourceManager HA

    1.集群规划 1.1 规划说明 hadoop1 cluster1 nameNode hadoop2 cluster1 nameNodeStandby ZooKeeper ResourceManager ...

  5. Q&As:1.cocos2d-html5如何获得鼠标划过事件

    不喜欢按部就班学东西,感觉各种框架各种技术就应该是拿到手用的,这应该是导致我现在学了这么多却没一样精通的缘故吧. 发现自己喜欢在QQ群回答一些菜鸟的问题,就算自己不清楚也会乐意看代码帮助解决╮(╯_╰ ...

  6. C#--Switch Case语句的返回

    C#中switch case语句的返回不只是用break关键字,break语句是用来阻止贯穿的最常见的方式.也可以用其他语句来替代它.如下面代码所示 static int Main(string[] ...

  7. DB知识点记录

    DB知识点记录 分页 SqlServer:ROW_NUMBER () over (ORDER BY ID) AS RN, MySql:limit Oracle:ROWNUM AS RN 数据表的基本结 ...

  8. Spring学习(十三)-----Spring 表达式语言(Spring EL)

    本篇讲述了Spring Expression Language —— 即Spring3中功能丰富强大的表达式语言,简称SpEL.SpEL是类似于OGNL和JSF EL的表达式语言,能够在运行时构建复杂 ...

  9. 《零基础学JavaScript(全彩版)》学习笔记

    <零基础学JavaScript(全彩版)>学习笔记 二〇一九年二月九日星期六0时9分 前期: 刚刚学完<零基础学HTML5+CSS3(全彩版)>,准备开始学习JavaScrip ...

  10. 3星|麦肯锡合伙人《从1到N》:PPT讲稿,图表不错,讲解不够深入

    从1到N:企业数字化生存指南 两位作者是麦肯锡合伙人.全书插图比较多,图做的还比较有水平.但是相关文字不够深入,我读后的感觉是:图表不是两位执笔者做的,他们对细节不清楚,对图表涉及到的行业也缺乏深入的 ...