题面 。
看到这道题,我第一反应就是莫队 。简化题意:给一个序列,给一些询问,每个询问包含三个区间代表序列的三个子序列,要求出这三个对应子序列去掉三个子序列都具有的公共数字后剩下的数字个数 。
我甚至也猜出了把所有询问的三个区间压到一起处理然后分别计算对应询问答案 。
但是,这么复杂的贡献用什么东西存?难道要开一个数组 query_appear_time[ 100000 ][ 100000 ]?
于是我打消了这个念头,最后还是看题解做的 。
令三个区间为a1,a2,a3 。
要求的答案就是a1数字个数-公共数字个数+a2数字个数-公共数字个数+a3数字个数-公共数字个数 。
即a1,a2,a3数字个数之和-公共数字个数*3 。
数字个数之和就是区间长度,那么问题就转化为了求三个区间的公共数字个数 。
对序列进行离散化,原本的1e9值域就变成了1e5值域 。
然后读入所有的询问区间按标准莫队进行排序,处理每个区间并且将其所有的数字出现个数转移到对应询问中 。接下来合并 。
到这一步其实并不难想,关键在于怎么求公共数字个数 。
前面我提到显然存不了一个query_appear_time[ 100000 ][ 100000 ],但是有一个数据结构叫bitset 。
bitset的一位只占一个比特,相当于一个二进制数,可以进行位运算 。
我们可以用bitset一位代表一个数字存储区间内值域,这在 洛谷3674小清新人渣的本愿 中十分有用 。
但是这题要存储区间每个数字个数 。
做法:
我们令p[i]为整个序列内小于等于i的数字的个数,cnt[i]为数字i在 当前区间 中出现的次数,建一个bitset<1e5>range 。
每当读入一个数字x我们令range[p[x]-cnt[x]]为1并令cnt[x]++,每当查询完一个区间我们用这样一个bitset记录这个区间 。
代表两个区间的bitset与(&)一下然后用range.count()求出新bitset的1个数,就是这两个区间的公共数字个数 。
原理:
如下图
对于一个区间,对于数字i,这个区间的bitset第p[ i ]位到第p[ i ]-cnt[ i ]位为1,公共数字个数就是min(cnt[i])
那么对于两个数的min(cnt[i]),p[i]~p[i]-min(cnt[i])+这个范围肯定全是1.
取与之后,p[ i ]~p[i-1]+1位的1的数量就是公共的i的出现次数 。
文章插图
至于计算每个区间的bitset,这么一大堆区间放在一起,显然可以莫队 。
然后分别计算每个区间的bitset,再对每个询问,将其三个区间的bitset与起来求1的个数,就是这个询问所要求的三个区间的公共元素数量 。
莫队操作,tmp为当前区间状态 。
for(int i=2;i<=m;i++){while(l>q[i].l)l--,tmp[p[a[l]]-tim[a[l]]]=1,tim[a[l]]++;while(r<q[i].r)r++,tmp[p[a[r]]-tim[a[r]]]=1,tim[a[r]]++;while(l<q[i].l)tim[a[l]]--,tmp[p[a[l]]-tim[a[l]]]=0,l++;while(r>q[i].r)tim[a[r]]--,tmp[p[a[r]]-tim[a[r]]]=0,r--;if(!vis[(q[i].pos-1)/3+1])res[(q[i].pos-1)/3+1]=tmp,vis[(q[i].pos-1)/3+1]=1;//神奇的O(1)复制elseres[(q[i].pos-1)/3+1]&=tmp;}合并答案
for(int i=1;i<=m;i++){int pub=res[i].count();printf("%d\n",cnt[i]-pub*3);}至此,我们已经得到了一个时间复杂度n3/2的莫队算法 。
但是还有一个问题 。
bitset虽然小,但是一个bitset要开1e5位 。这样的bitset开不了题目要求的1e5个(每个询问要存三个区间) 。
那么就只开询问数量的一部分(我开的1/4)的bitset,然后一次处理问题的一小部分就可以了 。
文章插图
经验总结扩展阅读
- .NET性能优化-是时候换个序列化协议了
- 华为云 MRS 基于 Apache Hudi 极致查询优化的探索实践
- Sql Server性能排查和优化懒人攻略
- 附踩坑记录 我用EasyExcel优化了公司的导出
- .NET性能优化-复用StringBuilder
- UE优化性能
- 哪个星座需要优化感情
- 前端性能优化——首屏时间&&白屏时间
- 「MySQL高级篇」explain分析SQL,索引失效&&常见优化场景
- 完全背包问题 —— 贪心优化 DP 范围