【 Gym - 101138D 】Strange Queries (莫队算法)

BUPT2017 wintertraining(15) #4B
Gym - 101138D

题意

a数组大小为n。(1 ≤ n ≤ 50 000) (1 ≤ q ≤ 50 000)(1 ≤ ai ≤ n)
q个查询,询问两个区间相同的数有多少对。

题解

[sl,sr]和[tl,tr]区间相同的数的对数可以用\(f[sl,tr]-f[sl,tl]-f[sr,tr]+f[sr,tr]\)计算。\(f[l,r]\)为区间[l,r]内相同的数的对数。
对于每个询问,记录需要计算的f的区间,然后按l/sqrt(n)为第一关键字,r为第二关键字排序。
如果计算完f[l,r],那么计算f[l1,r1]时,可以由[l,r]区间转移到[l1,r1]区间,相当于移动左右端点的指针。

随便写写的时间复杂度分析(n为计算的区间个数):

pos[i]记录区间i的第一关键字。
相邻的两个计算区间(排序后),若pos相同,左指针移动最远\(\sqrt n\)步,最坏情况就是n个区间都移动这么多步,总的最多\(n\sqrt n\)步。
若pos不同,总的最多是\(n\)步。
pos相同的所有区间,右指针最多共移动n步(1到n),共\(\sqrt n\)个pos值,总的最多移动\(n\sqrt n\)步。
pos不同时,右指针最多移动n步(n到1),共\(\sqrt n\)个pos,总的最多移动\(n\sqrt n\)步。
因为计算所有区间的过程,左指针最多移动\(n\sqrt n\)步,右指针最多移动了\(n\sqrt n\)步。因此复杂度是\(O(n\sqrt n)\)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
#define N 50005
#define ll long long
using namespace std;
int n,m,a[N],qs,pos[N];
ll ans[N],s[N];
struct node{int l,r,d;}p[N<<2];
bool cmp(node a,node b){
    return pos[a.l]<pos[b.l]||pos[a.l]==pos[b.l]&&a.r<b.r;
}
void add(int p,ll &f){
    f+=s[a[p]]++;
}
void sub(int p,ll &f){
    f-=--s[a[p]];
}
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    for(int i=1,j=1;i<=n;i++){
        if(i%(int)sqrt(n)==0)j++;
        pos[i]=j;
    }
    scanf("%d",&qs);
    for(int i=1;i<=qs;i++){
        int sl,sr,tl,tr;
        scanf("%d%d%d%d",&sl,&sr,&tl,&tr);
        sr++;tl--;
        //[sl,tr]-[sl,tl-1]-[sr+1,tr]+[sr+1,tl-1]
        p[m++]=(node){sl,tr,i};p[m++]=(node){sl,tl,-i};
        p[m++]=(node){sr,tr,-i};p[m++]=(node){sr,tl,i};
    }
    sort(p,p+m,cmp);
    int L=n+1,R=n;
    ll num=0;
    for(int i=0;i<m;i++){
        while(L<p[i].l)
            sub(L++,num);
        while(L>p[i].l)
            add(--L,num);
        while(R>p[i].r)
            sub(R--,num);
        while(R<p[i].r)
            add(++R,num);
        if(p[i].d>0)ans[p[i].d]+=num;
        else ans[-p[i].d]-=num;
    }
    for(int i=1;i<=qs;i++)printf("%lld\n",ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/flipped/p/6408169.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值