[BZOJ3781]:小B的询问(离线莫队算法)
题目传送门
题目描述
小B有一个序列,包含$N$个$1~K$之间的整数。他一共有$M$个询问,每个询问给定一个区间$[L...R]$,求$\sum \limits_{i=1}^{K}c(i)^2$的值,其中$c(i)$表示数字$i$在$[L...R]$中的重复次数。小$B$请你帮助他回答询问。
输入格式
第一行,三个整数N,M,K。
第二行,N个整数,表示小B的序列。
接下来的M行,每行两个整数L,R。
输出格式
M行,每行一个整数,其中第i行的整数表示第i个询问的答案。
样例
样例输入
6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6
样例输出
6
9
5
2
数据范围与提示
对于全部的数据,$1\leqslant N,M,K\leqslant 50,000$。
题解
惊喜的发现是要求离线,而且数据范围为$50,000$,较为宽松,所以考虑莫队。
先将序列分块,然后将询问排序,不断伸缩左右端点,小心不要弄错就好了。
真不知道有什么好说的了……
代码时刻
#include<bits/stdc++.h>
using namespace std;
struct rec
{
int id;
int l;
int r;
int pos;
}q[50001];
int a[50001];
int cnt[50001];
long long ans;
long long sum[50001];
bool cmp(rec a,rec b){return a.pos==b.pos?a.r<b.r:a.pos<b.pos;}//排序
int main()
{
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
int t=sqrt(n);//分块
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].pos=(q[i].l-1)/t+1;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++)//四种情况伸缩左右端点
{
while(l<q[i].l)
{
ans-=2*cnt[a[l]]-1;//注意答案的加减
cnt[a[l]]--;
l++;
}
while(l>q[i].l)
{
l--;
ans+=2*cnt[a[l]]+1;
cnt[a[l]]++;
}
while(r<q[i].r)
{
r++;
ans+=2*cnt[a[r]]+1;
cnt[a[r]]++;
}
while(r>q[i].r)
{
ans-=2*cnt[a[r]]-1;
cnt[a[r]]--;
r--;
}
sum[q[i].id]=ans;
}
for(int i=1;i<=m;i++)
printf("%lld\n",sum[i]);
return 0;
}
rp++