目录

  • 解题思路

    • 裸的想法
    • 优化一下线性基的储存
    • 另外两种优化思路
  • 源代码

假的题面
可用的题面
离线BZOJ好啊

  • Time limit 30000ms
  • Memory limit 131072 kB
  • OS Linux

解题思路

根据标签可知,这题要用线段树维护。

裸的想法

线段树上每个点都是一个线性基,这个线段树就维护时间轴。对于时间轴上的某一点,线段树维护的是这个点时可用的数字构成的线性基。我们可以在取出某个数字的时候,将它加到线段树上对应的时间段。但是会发现pushdown和pushup极慢,而且很不方便,于是标记永久化(我再这题才第一次使用标记永久化这个科技),在查询的时候统计标记。实测下来,20个点里有一半的数据量达到最大值。这个思路在大些的这一半测试点上,占用内存约148MB,耗时2s左右,20个点全部跑下来需要20s,于是显然MLE了,要不是BZOJ只看总时间,那还TLE了。下面这个是使用传统的线性基的MLE代码——

#include<map>
#include<vector>
#include<cstdio>
#include<algorithm>const int MAXN=5e5+3;
const int wide=31;int n;
int last[MAXN];//记录上一个被插入的相同的数字的时间戳
std::map<int,int> mp;//查询该数被上一次加入的时间戳struct Linear{//codelfint p[33]={0};void insert(int x){for(int i=wide;~i;i--){if((x>>i)&1){if(p[i]) x^=p[i];else { p[i]=x; return; }}}}int quemx(){int ans=0;for(int i=wide;~i;i--)ans=std::max(ans,ans^p[i]);return ans;}void add(const Linear & s)//合并一个线性基进来  //希望这种引用能加速传参,防止传参传一整个结构体……目前还不知道可不可行,留坑,啥时候问问{for(int i=wide;~i;i--){if(s.p[i]) insert(s.p[i]);}}
};Linear t[MAXN<<2];//线段树
void update(int x,int l,int r,int ql,int qr,int k)
{if(ql<=l&&r<=qr){t[x].insert(k);return;}int mid=l+r>>1;if(ql<=mid) update(x<<1,l,mid,ql,qr,k);if(qr>mid) update(x<<1|1,mid+1,r,ql,qr,k);
}void queans(int x,int l,int r,Linear sum)//遍历整棵树,输出答案
{sum.add(t[x]);//收集标记if(l==r){printf("%d\n",sum.quemx());return;}int mid=l+r>>1;queans(x<<1,l,mid,sum);//先序遍历保证输出答案的顺序queans(x<<1|1,mid+1,r,sum);
}int main()
{//freopen("test.in","r",stdin);scanf("%d",&n);for(int i=1,a;i<=n;i++){scanf("%d",&a);if(a<0){update(1,1,n,mp[-a],i-1,-a);mp[-a]=last[mp[-a]];}else{if(mp.find(a)!=mp.end()) last[i]=mp[a];//上一次出现的位置else last[i]=0;mp[a]=i;//记录加入的时间戳}}std::map<int,int>::iterator it;for(it=mp.begin();it!=mp.end();it++){if(it->second){update(1,1,n,it->second,n,it->first);}}Linear temp;queans(1,1,n,temp);return 0;
}

优化一下线性基的储存

(思路来源)实测那个博主的代码跑大的测试点时内存约72MB,时间都在2.3s左右。内存少了一半。

可以想象,由于标记永久化和区间长度不大,线段树上那些线性基,会有很多空行,有些线性基甚至是全空的,于是弃用固定长度为33的int数组表示每一个线性基,换用变长数组vector。这样的线性基,数组下标为\(i\)的那一个向量,最高位的1就不一定在从右向左第\(i\)位上了。换句话说,这种新的线性基,可以看成把原来那种线性基里的0向量全部去掉,剩下的全是非0向量。

从思路来源那个链接里学到了新的线性基插入方法,可以在这种变长数组的情况下插入元素——依然从高到低(从大到小)扫描线性基中已有的向量\(p[i]\),和想要插入的元素\(x\)对比,如果\((x \text^ p[i])<x\),即异或上\(p[i]\)能够减小\(x\),那么就把\(x\)异或上\(p[i]\)。扫描完现有线性基,x和每一个向量都进行比较之后,如果\(x\)为0,说明插入失败或者说不用插入,否则就把\(x\)给push_back进线性基里,然后类似选择排序,把\(x\)移动到合适的位置,以保持线性基里的数字是递增的。

结合代码看一下这个过程——

std::vector<int> p;
void insert(int x)
{for(int i=p.size()-1;~i;i--)if((p[i]^x)<x) x^=p[i];if(x){p.push_back(x);for(int i=p.size()-1;i;i--){if(p[i]<p[i-1]) std::swap(p[i],p[i-1]);}}
}

我来尝试说明一下,两种处理\(x\)的方法是等效的。

回忆一下传统的线性基里,向量\(p[i]\)如果是非零向量,那么其二进制第\(i\)位(同时也是其最高位)一定是1,换句话说,线性基里的所有行向量的最高位1一定排在线性基组成的01矩阵的主对角线上。

我们在传统线性基中插入数字\(x\)时,如果\(x\)第\(i\)位是1,那么就让\(x\)异或上\(p[i]\),使\(x\)在这一位的1被去掉。由于从高到低处理,所以\(x\)在这个过程被处理的总是最高位,最高位总是不断向后退,\(x\)总是变小。这个过程持续到\(x\)变为0,或者发现线性基里没有这一位为1的向量,无法把\(x\)最高位清零,于是就把\(x\)塞到这个位置,\(x\)的最高位1现在依然在主对角线上。

在压缩过的线性基里插入数字\(x\),依然从高到低扫。如果向量\(p[i]\)能够减小\(x\),那就两种情况——

  • p[i]的最高位和x的最高位位置一致

    • 这种大概就是传统线性基里的操作了吧,将x的当前最高位清零,x肯定变小了。
  • p[i]的最高位低于x的最高位

    • 出现这种情况,说明比p[i]大的那些向量都没能把x的当前最高位清零,那么p[i]和比p[i]小的向量更不可能清零x的当前最高位了,x能加入线性基是石锤了。这时候x^=p[i],让x变成了x^p[i],仅只是通过清零了x的一部分低位,让x减小,对x的最高位没有影响。这时直接将\(x\)push_back进去得到的新线性基,和将\(x\)处理到最后一个向量p[i]再push_back得到的线性基是等价的。所以可以及时break出来,剩下那些更小的向量都不用看了。思路来源 没有及时break,所以这里还可以继续优化一下——

      std::vector<int> p;void insert(int x){for(int i=p.size()-1;~i;i--){if((p[i]^x)<x) x^=p[i];else if(p[i]<x) break;//加了这行}if(x){p.push_back(x);for(int i=p.size()-1;i;i--)if(p[i]<p[i-1])std::swap(p[i],p[i-1]);}}

      最慢的点2.25s,其他大的测试点基本2.13s,差不多优化了0.2s,感觉效果还是挺好的。

  • p[i]的最高位不可能高于x的最高位,那样不能减小x。

另外两种优化思路

  • yyb博客

    大的测试点69MB、1.7s

    首先是使用了快读。

    在处理输入数据时使用sort、unique、lower_bound这套进行离散化,而不用速度慢的map。

    把线段树上的节点从线性基换成vector,更新区间时直接将数字push_back进对应线段树节点的vector里,而不是把数字塞进几个线性基。

    查询时收集标记是这么做的:把当前节点vector里的数字全部取出来塞到用于统计永久化标记的线性基里。

    线性基多存一个变量,统计线性基内非零向量的数量,如果当前线性基向量数量已经达到32,就及时return。乍一看这个优化是最关键的,能十分有效地避免前面几个优化的缺点,但实测去掉这个优化还是1.7s…………

  • claris博客

    emmm……还没看懂,内存45MB左右,大的测试点耗时1.3s。。。orz…

现在这个todolist又多了一个√

  • [x] CodeForces 1100F Ivan and Burgers 单纯询问区间异或最大值
  • [x] HDU 6579 Operation 多了个末尾插入数据的操作,还有强制在线
  • [x] BZOJ 4184 shallot 这题要支持插入和删除的操作,询问是整个序列。居然是权限题……本地测一下算了。
  • [ ] UVALive 8514 XOR 2017ICPC西安的一道题,询问区间异或值或上k的最大值

源代码

#include<map>
#include<vector>
#include<cstdio>
#include<algorithm>const int MAXN=5e5+3;
const int wide=31;int n;
int last[MAXN];//记录上一个被插入的相同的数字的时间戳
std::map<int,int> mp;//查询该数被上一次加入的时间戳struct Linear{//codelfstd::vector<int> p;void insert(int x){for(int i=p.size()-1;~i;i--)if((p[i]^x)<x) x^=p[i];if(x){p.push_back(x);for(int i=p.size()-1;i;i--){if(p[i]<p[i-1]) std::swap(p[i],p[i-1]);}}}int quemx(){int ans=0;for(int i=p.size()-1;~i;i--)ans=std::max(ans,ans^p[i]);return ans;}void add(const Linear & s)//合并一个线性基进来{for(int i=s.p.size()-1;~i;i--){if(s.p[i]) insert(s.p[i]);}}
};Linear t[MAXN<<2];//线段树  //不存l、r,初值全部为0,就不build了
void update(int x,int l,int r,int ql,int qr,int k)
{//不用复制父亲的,也不用下传给儿子,只因标记永久化,询问答案时用一个参数统计就好if(ql<=l&&r<=qr){t[x].insert(k);return;}int mid=l+r>>1;if(ql<=mid) update(x<<1,l,mid,ql,qr,k);if(qr>mid) update(x<<1|1,mid+1,r,ql,qr,k);
}void queans(int x,int l,int r,Linear sum)//遍历整棵树,输出答案
{sum.add(t[x]);if(l==r){printf("%d\n",sum.quemx());return;}int mid=l+r>>1;queans(x<<1,l,mid,sum);//先序遍历保证输出答案的顺序queans(x<<1|1,mid+1,r,sum);
}int main()
{//freopen("test.in","r",stdin);scanf("%d",&n);for(int i=1,a;i<=n;i++){scanf("%d",&a);if(a<0){update(1,1,n,mp[-a],i-1,-a);mp[-a]=last[mp[-a]];}else{if(mp.find(a)!=mp.end()) last[i]=mp[a];//上一次出现的位置else last[i]=0;mp[a]=i;//记录加入的时间戳}}std::map<int,int>::iterator it;for(it=mp.begin();it!=mp.end();it++){if(it->second){update(1,1,n,it->second,n,it->first);}}Linear temp;queans(1,1,n,temp);return 0;
}

转载于:https://www.cnblogs.com/wawcac-blog/p/11326593.html

BZOJ 4184 shallot相关推荐

  1. BZOJ.4184.shallot(线段树分治 线性基)

    BZOJ 裸的线段树分治+线性基,就是跑的巨慢_(:з」∠)_ . 不知道他们都写的什么=-= //41652kb 11920ms #include <map> #include < ...

  2. bzoj 4184 shallot 时间线建线段树+vector+线性基

    题目大意 n个时间点 每个时间点可以插入一个权值或删除一个权值 求每个时间点结束后异或最大值 分析 异或最大值用线性基 但是线性基并不支持删除操作 我们可以对时间线建一棵线段树 离线搞出每个权值出现的 ...

  3. BZOJ 4184: shallot

    Description 在某时刻加入或删除一个点,问每个时刻的集合中能异或出来的最大值是多少. Sol 线段树+按时间分治+线性基. 按时间分治可以用 \(logn\) 的时间来换取不进行删除的操作. ...

  4. BZOJ 4184 shallot 线性基+分治

    Description 小苗去市场上买了一捆小葱苗,她突然一时兴起,于是她在每颗小葱苗上写上一个数字,然后把小葱叫过来玩游戏. 每个时刻她会给小葱一颗小葱苗或者是从小葱手里拿走一颗小葱苗,并且让小葱从 ...

  5. BZOJ 4184: shallot 线性基+线段树分治

    复习一下线性基 ~ code: #include <cmath> #include <vector> #include <cstdio> #include < ...

  6. 4184: shallot

    4184: shallot Time Limit: 30 Sec   Memory Limit: 128 MB Submit: 263   Solved: 129 [ Submit][ Status] ...

  7. 【BZOJ 4184】shallot 线性基

    一开始一直没有想到因为只是在一直思考线性上怎么做,结果这道题很巧妙的运用的是在线段树上,因为每一个数字的影响其实只是在一段区间上的,这个=很显然,然后就类似于线段树上区间修改打懒惰标记,最后全部dfs ...

  8. LOJ 2312(洛谷 3733) 「HAOI2017」八纵八横——线段树分治+线性基+bitset

    题目:https://loj.ac/problem/2312 https://www.luogu.org/problemnew/show/P3733 原本以为要线段树分治+LCT,查了查发现环上的值直 ...

  9. []BZOJ4184: shallot

    题解:  考虑离线 对于每个数都有一个存在时间段 我们以时间段建线段树 因为线性基不允许删除元素 然后我们只需要把对应时间段的元素加入 这样就能避免删除问题 然后就是普通操作了(第一次写链表版本的线性 ...

最新文章

  1. POJ2492 A Bug s Life 题解
  2. java 小波去噪原理_小波去噪的基本知识
  3. sails的简单配置以及controller的使用
  4. 多媒体个人计算机必须硬件设备包括,计算机基础在线测试.doc
  5. matlab 日期加小时数_MATLAB时间与日期的基本操作
  6. LeetCode 20.有效括号
  7. 原生js、jQuery实现选项卡功能
  8. js 深拷贝 和 浅拷贝
  9. 【转】win7与ubuntu双系统,删除ubuntu后,启动错误error:no such partition grub rescue的修复--不错...
  10. 步进电机 高速光耦_干货!伺服电机和步进电机的31个技术问答
  11. 基于微信小程序外卖点餐系统 开题报告
  12. 证券交易4-PB系统简介
  13. 基于多租户的云计算Overlay网络
  14. 2020数学建模B题
  15. m选n组合的两种算法(C语言实现)
  16. 真香!微软办公环境大揭秘!
  17. 解灾转运方法,人人都很容易做得到!
  18. 2023 目标,与君共勉
  19. 鲁大师5月新机性能榜:红魔6R夺冠,“特供版”新机密集
  20. BGP高防IP如何防DDos和cc攻击?原理是什么?

热门文章

  1. 桔梗网导航怎么取消删除?分享三种方法...
  2. 如何让文章内容被百度快速收录
  3. 以太网PHY 开发与解析
  4. 金龟子现身年味儿活动 教小朋友拜年吉祥话
  5. html5 获取file文件绝对路径,H5中绝对路径和相对路径
  6. PHP 预防CSRF、XSS、SQL注入攻击
  7. 修改ip地址的软件是真的嘛_如何修改手机ip地址
  8. ffmpeg+jsmpeg+nginx实现多道h5视频直播
  9. 【性格心理学】为什么我总是难以拒绝别人?
  10. Python中range函数的使用