题意

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

    项目开发过程中经常使用了基于libuv库封装的库接口来实现异步处理,一直没仔细研究过这些接口的内部如何实现,因此也就没有掌握它的设计思想.今天花了点时间研究了其事件循环内部的一些过程,总算有了一些理解 ...

  2. PostgreSQL参数学习:vacuum_defer_clean_age

    官方文档: http://www.postgresql.org/docs/9.3/static/runtime-config-replication.html 为了防止slave端读取数据时,因为读到 ...

  3. Scrapy爬取美女图片续集 (原创)

    上一篇咱们讲解了Scrapy的工作机制和如何使用Scrapy爬取美女图片,而今天接着讲解Scrapy爬取美女图片,不过采取了不同的方式和代码实现,对Scrapy的功能进行更深入的运用.(我的新书< ...

  4. Maven学习(六)-----Maven仓库的详细介绍

    Maven仓库的详细介绍 在Maven中,任何一个依赖.插件或者项目构建的输出,都可以称之为构件.Maven在某个统一的位置存储所有项目的共享的构件,这个统一的位置,我们就称之为仓库.(仓库就是存放依 ...

  5. Django——test文件编写接口测试

    用自己建立的小网页来做接口测试,在Django的tests.py写下如下 test_login_page为用get方式登录login路径,根据回复验证是否查看到页面 test_login_action ...

  6. 第k小分数(二分值)

    //时间限制:10000ms //单点时限:1000ms //内存限制:256MB //描述 //给定N个不同的质数P1, P2, … PN.用它们作为分目可以组成(P1-1) + (P2-1) + ...

  7. cmake-index-3.11.4机翻

    index next | CMake » git-stage git-master latest release 3.13 3.12 3.11.4 3.10 3.9 3.8 3.7 3.6 3.5 3 ...

  8. LeetCode 148——排序链表

    1. 题目 2. 解答 2.1 快速排序 可参考 快速排序和归并排序 中的第一种快速排序思想,与在数组中排序有两点不同. 第一,我们需要取最后一个元素作为主元,在数组中可以直接访问到最后一个元素,但在 ...

  9. 2018软工实践—Alpha冲刺(3)

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作 协助后端界面的开发 搭建项目运行的服务器环境 ...

  10. Controller与Switch建立连接

    连接建立 控制器和交换机认识的过程. 用于交互Openflow版本,如果不同则没有后续. 同1. 特征请求,控制器询问交换机的特征信息. 交换机回复控制器,相当于把整个交换机的所有配置都告诉控制器了. ...