离线莫队算法

离线莫队算法

离线莫队算法用于解决区间查询问题,核心思想是,如果我们已知区间 [ l , r ] [l,r] [l,r]内的答案,如果我们能在 O ( 1 ) O(1) O(1)时间内转移到 [ l , r + 1 ] [l,r+1] [l,r+1] [ l , r − 1 ] [l,r-1] [l,r1] [ l − 1 , r ] [l - 1,r] [l1,r]或者 [ l + 1 , r ] [l + 1,r] [l+1,r]即相邻区间内,那么我们就可以通过这种区间转移的方式求出所有区间的答案,其核心还是尽可能的利用已知,而不是去重复求解。

区间转移

我们设计双指针算法,利用 l l l r r r变量标记区间的端点(闭区间),首先初始化令 l = 1 , r = 1 l=1,r=1 l=1,r=1 a n s = f ans=f ans=f f f f为区间 [ 1 , 1 ] [1,1] [1,1]的答案。之后我们便可以进行区间转移。

我们遍历每一个询问,然后将 l r lr lr移动到目的地,这里必须先扩大区间长度,然后再缩小区间长度,必须是这个顺序,否则 l r lr lr指针会出现交叉。

for (int i = 0; i < m; i++)
{
    Query curr = q[i];
    while (l > curr.l)
    {
        l--;
        // add l
    }

    while (r < curr.r)
    {
        r++;
        // add r
    }

    while (l < curr.l)
    {
        // delete l
        l++;
    }

    while (r > curr.r)
    {
        // delete r
        r--;
    }

    ans[curr.id] = sum;
}

离线查询与区间分块

如果我们按照题目给出的区间查询顺序进行区间转移,那时间复杂度将是巨大的,例如给出 [ 1 , 3 ] [1,3] [1,3] [ 888 , 999 ] [888,999] [888,999] [ 1 , 4 ] [1,4] [1,4],如果我们按照这个顺序查询,那么我们必须经过两次的大转移,如果我们将 [ 1 , 3 ] [1,3] [1,3] [ 1 , 4 ] [1,4] [1,4]放在一起的话,我们只需要经过一次大转移即可,我们应该对查询进行离线,然后排序以降低时间复杂度。

首先我们对区间进行分块,给每一个区间内的点分配一个所在区间的编号,代码如下:

int q_n = sqrt(n) + 1;
for (int i = 1; i <= n; i++)
{
    scanf("%lld", arr + i);
    block[i] = i / q_n;
}

其中 n n n为区间大小。我们先以块大小为 n \sqrt{n} n 进行分块,块的数量为 n \sqrt{n} n

之后我们对查询进行排序,排序规则是以 l l l所在的块编号为第一关键字, r r r为第二关键字。

struct Query
{
    int l;
    int r;
    int id;

    bool operator<(const Query &o) const
    {
        if (block[l] != block[o.l])
        {
            return block[l] < block[o.l];
        }
        else
        {
            return r < o.r;
        }
    }
} q[50005];

排序结束后,我们按照排序的结果进行区间转移即可。

奇偶排序

有时候我们对于相邻区间的 r r r进行交叉排序,如:

struct Query
{
    int l;
    int r;
    int id;

    bool operator<(const Query &o) const
    {
        if (block[l] != block[o.l])
        {
            return block[l] < block[o.l];
        }
        else if (block[l] & 1)
        {
            return r < o.r;
        }
        else
        {
            return r > o.r;
        }
    }
} q[50005];

这样我们就能很好的处理,折返的情况,

折返

有时候也存在负优化的情况,具体情况具体分析。

所以说想卡莫队非常容易,我们只需要让 r r r变化幅度大一些,即可。

例题

P2709

#include <bits/stdc++.h>

#define FR freopen("in.txt", "r", stdin)

typedef long long ll;
using namespace std;

int block[50005];
ll arr[50005];
ll cnt[50005];
ll ans[50005];

int n, m, k;

struct Query
{
    int l;
    int r;
    int id;

    bool operator<(const Query &o) const
    {
        if (block[l] != block[o.l])
        {
            return block[l] < block[o.l];
        }
        else if (block[l] & 1)
        {
            return r < o.r;
        }
        else
        {
            return r > o.r;
        }
    }
} q[50005];

int main()
{
    scanf("%d %d %d", &n, &m, &k);
    int q_n = sqrt(n) + 1;
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", arr + i);
        block[i] = i / q_n;
    }

    for (int i = 0; i < m; i++)
    {
        int l, r;
        scanf("%d %d", &l, &r);
        q[i].l = l;
        q[i].r = r;
        q[i].id = i;
    }

    sort(q, q + m);
    int l = 1, r = 1;
    ll sum = 1;
    cnt[arr[1]]++;
    for (int i = 0; i < m; i++)
    {
        Query curr = q[i];
        while (l > curr.l)
        {
            l--;
            sum += 2 * cnt[arr[l]] + 1;
            cnt[arr[l]]++;
        }

        while (r < curr.r)
        {
            r++;
            sum += 2 * cnt[arr[r]] + 1;
            cnt[arr[r]]++;
        }

        while (l < curr.l)
        {
            sum += -2 * cnt[arr[l]] + 1;
            cnt[arr[l]]--;
            l++;
        }

        while (r > curr.r)
        {
            sum += -2 * cnt[arr[r]] + 1;
            cnt[arr[r]]--;
            r--;
        }

        ans[curr.id] = sum;
    }

    for (int i = 0; i < m; i++)
    {
        printf("%lld\n", ans[i]);
    }
    return 0;
}

带修改莫队

记录时间为第三维,即 [ l , r , t i m e ] [l,r,time] [l,r,time]在三个维度之间转换,分块对 l l l r r r进行分块,分块大小为 n t 3 \sqrt[3] {nt} 3nt ,当 t = 0 t=0 t=0的时候,分块大小为 n \sqrt{n} n

P1903

#include <bits/stdc++.h>

using namespace std;

#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)

typedef long long ll;

int n, m;

int arr[133335];

int block;

#define PT(x) (x / block)

struct Modification
{
    int last;
    int idx;
    int col;
} mod[133335];

struct Query
{
    int time;
    int l, r;
    int idx;

    bool operator<(const Query &o) const
    {
        if (PT(l) != PT(o.l))
            return PT(l) < PT(o.l);
        if (PT(r) != PT(o.r))
            return PT(r) < PT(o.r);
        return time < o.time;
    }
} que[133335];

int ti = 0;
int id = 0;

int cnt[1000005];
int Ans[133335];

int main()
{
    scanf("%d %d", &n, &m);

    for (int i = 1; i <= n; i++)
    {
        scanf("%d", arr + i);
    }

    for (int i = 0; i < m; i++)
    {
        char op;
        int a, b;
        scanf("\n%c%d%d", &op, &a, &b);
        if (op == 'Q')
        {
            id++;
            que[id].l = a;
            que[id].r = b;
            que[id].time = ti;
            que[id].idx = id;
        }
        else
        {
            ti++;
            mod[ti].idx = a;
            mod[ti].last = arr[a];
            mod[ti].col = b;
            arr[a] = b;
        }
    }

    for (int i = ti; i > 0; i--)
    {
        arr[mod[i].idx] = mod[i].last;
    }

    if (ti == 0)
    {
        block = ceil(sqrt(n));
    }
    else
    {
        block = ceil(exp((log(n) + log(ti)) / 3));
    }

    sort(que + 1, que + id + 1);
    int l = 0, r = 0, t = 0;
    int ans = 0;
    for (int i = 1; i <= id; i++)
    {
        while (l > que[i].l)
        {
            l--;
            ans += !cnt[arr[l]];
            cnt[arr[l]]++;
        }

        while (r < que[i].r)
        {
            r++;
            ans += !cnt[arr[r]];
            cnt[arr[r]]++;
        }

        while (l < que[i].l)
        {
            cnt[arr[l]]--;
            ans -= !cnt[arr[l]];
            l++;
        }

        while (r > que[i].r)
        {
            cnt[arr[r]]--;
            ans -= !cnt[arr[r]];
            r--;
        }

        while (t > que[i].time)
        {
            if (mod[t].idx >= l && mod[t].idx <= r)
            {
                cnt[arr[mod[t].idx]]--;
                ans -= !cnt[arr[mod[t].idx]];
            }

            arr[mod[t].idx] = mod[t].last;

            if (mod[t].idx >= l && mod[t].idx <= r)
            {
                ans += !cnt[arr[mod[t].idx]];
                cnt[arr[mod[t].idx]]++;
            }
            t--;
        }

        while (t < que[i].time)
        {
            t++;
            if (mod[t].idx >= l && mod[t].idx <= r)
            {
                cnt[arr[mod[t].idx]]--;
                ans -= !cnt[arr[mod[t].idx]];
            }

            arr[mod[t].idx] = mod[t].col;

            if (mod[t].idx >= l && mod[t].idx <= r)
            {
                ans += !cnt[arr[mod[t].idx]];
                cnt[arr[mod[t].idx]]++;
            }
        }

        Ans[que[i].idx] = ans;
    }

    for (int i = 1; i <= id; i++)
    {
        printf("%d\n", Ans[i]);
    }

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值