解决什么问题?
给定n个数a[n],q次查询,每次查询区间[l,r]
情景带入https://www.luogu.com.cn/problem/SP3267
长度为n的序列a[n],q次询问,每次询问区间[l,r]内有多少个不同的数字
例
5 1 1 2 1 3 3 1 5 2 4 3 5
输出
3 2 3
一般解法
每次查询都要遍历整个查询区间。一个不少
每次查询[l,r],把区间里的数放入一个set,set的大小就是答案
优化一点
每次查询不经过整个区间。有公共部分少了一点公共部分
规则:引入两个下标L,R,这两个下标,随每次查询移动,移动到该次查询区间为止。初始化L=1,R=0.(想想为什么)
举例说明(两次查询)
[1,6] [2,7];(明显两个区间有公共部分[2,6]),我们能共用的前提是,这两次查询是相邻的(自己想想)
根据规则,初始化如下
R | L | ||||||||
---|---|---|---|---|---|---|---|---|---|
下标0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a[i] |
第一次查询[1,6],我们要移动L和R,使[L,R]是[1,6]。
L已经在1上,无需移动,我们移动R,最后:
L | R | ||||||||
---|---|---|---|---|---|---|---|---|---|
下标0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a[i] |
第二次查询[2,7],此时L在1、R在6。我们要移动L和R,使[L,R]是[2,7]。
L从1移动到2:
L | R | ||||||||
---|---|---|---|---|---|---|---|---|---|
下标0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a[i] |
R从6移动到7:
L | R | ||||||||
---|---|---|---|---|---|---|---|---|---|
下标0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a[i] |
我们发现在第二次移动次数少了,没有经过整个区间。
再优化一点
公共部分再多一点
我们发现可以把q次查询,打乱顺序,让公共部分更多(前提是这q次查询不分先后)
打乱规则
易想到,可以把L按照从小到大排序,这样L就无需过度移动,但R又是无头苍蝇乱撞了。反之同理。
不能极端,那就折中,让L少走一点弯路,R也少走一点弯路。
那么L既然不能一点弯路都不走,那我们就让她走,少走一点,给他画个区,让他在区里走一点弯路。
分块
把n个数字,划分成√n个块,编号为1-√n。
排序规则,按照L的分块区,从小到大排序,同一区,按照R从小到大排序。
感觉这和之前先排L,没有发生多大变化呀?
变化大了去了:之前先拍L,那么R也就固定下来了。
现在L在同一区,我们不看L了,看R,也就是说L大的,R却小,那就先查询你。
如果还不理解为什么快了:
同一个区里,R是从小到大的,L在区里移动,一个区大小是√n。
那么一个区的R移动最坏也就是n,有√n个区,n√n。
一个区L最坏也是k√n(反复横跳),(k常数是该区L个数),跳完之后跨区是√n,有√n个区。现在理解了吧。
总共是3个√n,少了整整一个√n的算法,极大优化。
再优化一点点
奇偶交叉
排序规则,L在同一奇数区,R按照从小到大。否则,R按照从大到小。
原理就是,跳完奇数区,R是大的,再跳偶数,顺便把偶数跳了。
代码时刻
针对例题
分块技术
siz=sqrt(n); bnum=ceil((double)n/siz); for(int i=1;i<=bnum;i++) { for(int j=(i-1)*siz+1;j<=i*siz;j++) { bein[j]=i; } }
移动技术
void add(ll pos) { if(!cnt[a[pos]]) ++now; ++cnt[a[pos]]; } void del(ll pos) { --cnt[a[pos]]; if(!cnt[a[pos]]) --now; }
排序法则(核心)
bool cmp(query x,query y) { return bein[x.l]==bein[y.l]?x.r<y.r:bein[x.l]<bein[y.l]; }
奇偶横跳
bool cmp(query x,query y) { return (bein[x.l]^bein[y.l])?bein[x.l]<bein[y.l]:(bein[x.l]&1)?x.r<y.r:x.r>y.r; }
常数压缩
while(l < ql) now -= !--cnt[a[l++]]; while(l > ql) now += !cnt[a[--l]]++; while(r < qr) now += !cnt[a[++r]]++; while(r > qr) now -= !--cnt[a[r--]];
完整代码
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll N=1e6+5; ll a[N],cnt[N],bein[N],ans[N],now; struct query{ ll l,r,id; }q[N]; // bool cmp(query x,query y) // { // return bein[x.l]==bein[y.l]?x.r<y.r:bein[x.l]<bein[y.l]; // } bool cmp(query x,query y) { return (bein[x.l]^bein[y.l])?bein[x.l]<bein[y.l]:(bein[x.l]&1)?x.r<y.r:x.r>y.r; } void add(ll pos) { if(!cnt[a[pos]]) ++now; ++cnt[a[pos]]; } void del(ll pos) { --cnt[a[pos]]; if(!cnt[a[pos]]) --now; } void solve() { ll n,m,siz,bnum; cin>>n; siz=sqrt(n); bnum=ceil((double)n/siz); for(int i=1;i<=bnum;i++) { for(int j=(i-1)*siz+1;j<=i*siz;j++) { bein[j]=i; } } for(int i=1;i<=n;i++) { cin>>a[i]; } cin>>m; for(int i=1;i<=m;i++) { cin>>q[i].l>>q[i].r; q[i].id=i; } sort(q+1,q+1+m,cmp); ll l=1,r=0; for(int i=1;i<=m;i++) { int ql=q[i].l,qr=q[i].r; while(l<ql) del(l++); while(l>ql) add(--l); while(r<qr) add(++r); while(r>qr) del(r--); ans[q[i].id]=now; } for(int i=1;i<=m;i++) { cout<<ans[i]<<'\n'; } } int main() { ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); int t=1;//cin>>t; while(t--) { solve(); } }