P2709-小B的询问-(莫队算法)

莫队算法被称为优雅的暴力,是一种 毒瘤暴力的 区间操作算法。初学莫队,记录一下思想。

对于多个区间查询[l,r]这类问题,可以离线操作,常规做法还有对左区间或者右区间从小到大排序,让左指针或者右指针只走一遍,有效降低时间复杂度。但是遇到[1,1000000],[2,3],[3,1000000],[4,5],[5,1000000]这样的查询区间,虽然可以保证左区间只扫一遍,但是右指针需要从头到尾来回扫,时间复杂度一下子就上去了。莫队算法就是专门针对这种毒瘤题目的。

它的主要思想是分块,对区间的范围n开根号得到一个d,每一个查询区间[l,r]的左区间l除以d,区间看作是全局的第x块,不同块按的区间块从小到大排序,同一块的按右区间从小到大排序(不同块看左,同块看右)。分块排序写一个自定义比较函数就可以了。时间复杂度n*sqrt(n),具体证明看其他大佬博客吧。

对于查询的区间[l,r],指针移动操作比较常规,无非是左指针和右指针左右移动,[5,7]的答案可以由[5,8]或者[5,6]移动一次右指针解决,也可以由[4,7]或[6,7]移动一次左指针解决。对于区间查询的贡献,减去原来的,加上现在的。

入门题P2709

题意看了很久才懂,解释一下数据,1到4出现了2个1,对答案贡献2*2,出现1个2,对答案贡献1*1,出现1个3,对答案贡献1*1,加起来就是6。

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<math.h>
#include<string>
#include<map>
#include<queue>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define inf 0x3f3f3f3f ///填充无限小-0x7f
using namespace std;

struct node
{
    int l;
    int d;
    int r;
    int idx;
    ll ans;
};
node b[50005];
int a[50005];
int num[50005];
int n,m,k;
int d;


bool cmp1(node p1,node p2)///分块,对左区间开根号,左边不在同一块按左区间小到大排序,左边在同一块按右边大小排序
{
    if(p1.d<p2.d)
        return true;
    else if(p1.d==p2.d)
        return p1.r<p2.r;
    else
        return false;
}

bool cmp2(node p1,node p2)///离线操作后恢复答案次序
{
    return p1.idx<p2.idx;
}


int main()///P2709 莫队入门
{
    scanf("%d%d%d",&n,&m,&k);
    d=sqrt(n);///开根号的分块标准
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    for(int i=1;i<=m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        b[i].l=l;
        b[i].d=l/d;///分块
        b[i].r=r;
        b[i].idx=i;
    }

    sort(b+1,b+m+1,cmp1);
    int L=1,R=1;
    num[ a[1] ]=1;
    ll ans=1;
    for(int i=1;i<=m;i++)
    {
        while(L>b[i].l)///左指针往左
        {
            L--;
            ans=ans-num[ a[L] ]*num[ a[L] ];///减去原来的计数贡献
            num[ a[L] ]++;
            ans=ans+num[ a[L] ]*num[ a[L] ];///加上新的计数贡献
        }
        while(L<b[i].l)///左指针往右
        {
            ans=ans-num[ a[L] ]*num[ a[L] ];
            num[ a[L] ]--;
            ans=ans+num[ a[L] ]*num[ a[L] ];
            L++;
        }
        while(R<b[i].r)///右指针往右
        {
            R++;
            ans=ans-num[ a[R] ]*num[ a[R] ];
            num[ a[R] ]++;
            ans=ans+num[ a[R] ]*num[ a[R] ];
        }
        while(R>b[i].r)///右指针往左
        {
            ans=ans-num[ a[R] ]*num[ a[R] ];
            num[ a[R] ]--;
            ans=ans+num[ a[R] ]*num[ a[R] ];
            R--;
        }
        b[i].ans=ans;
    }

    sort(b+1,b+m+1,cmp2);
    for(int i=1;i<=m;i++)
        printf("%lld
",b[i].ans);


    return 0;
}
原文地址:https://www.cnblogs.com/shoulinniao/p/12829251.html