莫队(区间查询)

解决什么问题?

给定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]),我们能共用的前提是,这两次查询是相邻的(自己想想)

根据规则,初始化如下

RL
下标0123456789
a[i]

第一次查询[1,6],我们要移动L和R,使[L,R]是[1,6]。

L已经在1上,无需移动,我们移动R,最后:

LR
下标0123456789
a[i]

第二次查询[2,7],此时L在1、R在6。我们要移动L和R,使[L,R]是[2,7]。

L从1移动到2:

LR
下标0123456789
a[i]

R从6移动到7:

LR
下标0123456789
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();
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值