Description
给你一个长度为N的序列ai,1≤i≤N和q组询问,每组询问读入l1,r1,l2,r2,需输出
![](http://www.lydsy.com/JudgeOnline/upload/201709/1.png)
get(l,r,x)表示计算区间[l,r]中,数字x出现了多少次。
先纪念第100篇博客啦啦啦~
第一步想到的是肯定可以通过某种方式,将多个区间的乘法消掉,改成加法或者单个区间的问题。我第一次推的时候把get1看成a[i],get2看成b[i],然后通过莫队求出Σa[i],Σa[i]^2,Σb[i],Σb[i]^2,推了一个小时都没有推出来,后来换成下面的方法就可以了。
由于get(l1,r1)可以变成get(1,r1)-get(1,l1-1),所以我们可以把式子转换为get(1,r1)-get(1,l1-1)*get(1,r2)-get(1,l2-1),整理一下得到get(1,r1)*get(1,r2)-get(1,r1)*get(1,l2-1)-get(1,l1-1)*get(1,r2)+get(1,l1-1)*get(1,l2-1),这样就之和区间的前缀和有关系了。
所以我们将每个询问拆成以上四组区间,用cntl[x]表示乘号左边区间内每个数字x出现的次数,用cntr[x]表示乘号右边区间内每个数字x出现的次数,将所有询问离线用莫队处理,由于添加一个数字,可以o(1)求出当前答案的变化量,如左边添加了一个x,那么当前cntl[x]*cntr[x]就变成了(cntl[x]+1)*(cntr[x]),即答案多了cntr[x],所以我们通过莫队离线处理每个区间,最后一并输出即可。
下附AC代码。
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<math.h>
#define maxn 50005
using namespace std;
typedef long long ll;
struct nod
{
int l,r,flag,id;
nod(int a,int b,int c,int d)
{
l=a;r=b;flag=c;id=d;
}
nod(){}
}query[maxn<<2];
int n,q,m;
int a[maxn];
int bel[maxn];
ll cntl[maxn],cntr[maxn];
ll ans[maxn];
bool operator<(nod a,nod b)
{
return bel[a.l]!=bel[b.l] ? bel[a.l]<bel[b.l] : a.r<b.r;
}
int main()
{
scanf("%d",&n);m=sqrt(n);bel[0]=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
bel[i]=i/m+1;
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
int l1,r1,l2,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
query[i]=nod(min(r1,r2),max(r1,r2),1,i);
query[i+q]=nod(min(r1,l2-1),max(r1,l2-1),-1,i);
query[i+q+q]=nod(min(l1-1,r2),max(l1-1,r2),-1,i);
query[i+q+q+q]=nod(min(l1-1,l2-1),max(l1-1,l2-1),1,i);
}
q=q<<2;
sort(query+1,query+1+q);
int nl=0,nr=0;ll res=0;
for(int i=1;i<=q;i++)
{
int l=query[i].l,r=query[i].r;
while(nr<r) nr++,cntr[a[nr]]++,res+=cntl[a[nr]];
while(nl<l) nl++,cntl[a[nl]]++,res+=cntr[a[nl]];
while(nr>r) res-=cntl[a[nr]],cntr[a[nr]]--,nr--;
while(nl>l) res-=cntr[a[nl]],cntl[a[nl]]--,nl--;
ans[query[i].id]+=(((ll)query[i].flag)*res);
}
q>>=2;
for(int i=1;i<=q;i++)
{
printf("%lld\n",ans[i]);
}
}