小B的询问
题目描述
小B有一个序列,包含(N)个(1)~(K)之间的整数。他一共有(M)个询问,每个询问给定一个区间([L..R]),求(sum^{K}_{i=1}{c(i)^2})的值,其中i的值从(1)到(K),其中(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 le N)、(M)、(K le 50000)
题解
一道莫队板题(说不定也可以用其他方法做,但是我太菜了,只会莫队)。
我们用(cnt[j])和(l,r)维护(l)到(r)区间内(j)这个数字的个数,并且维护一个(ans)值,然后我们移动(l)和(r)的时候快速地维护这个数组和(ans)值((ans)值先减去原本的值的平方,再加上新的值的平方)。
把式子化简之后就是:
- 如果(cnt_i)多了一个,那(ans+=2*cnt[x]+1)
- 如果(cnt_i)少了一个,那(ans-=2*cnt[x]-1)
然后我们把询问中(frac {l}{sqrt{n}})相同的数分为一组(这里分块的大小是(sqrt{n}),一般来说随机数据的话区(sqrt{n})会比较好,但是不同的题目分块的大小也可以不同)
然后在同一组内的询问,我们按照询问的(r)值从小到大排序。
这样排序后暴搜的时间复杂度就变成了(O(nsqrt{n}))了,能过此题。
上代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
int a[1000009];
struct aa{
int l,r,x,ans;
}p[1000009];
int sq;
bool cmp(aa x,aa y){
if(x.l/sq<y.l/sq) return 1;
if(x.l/sq>y.l/sq) return 0;
return x.r<y.r;
}
int cnt[1000009];
bool cmpp(aa x,aa y){return x.x<y.x;}
int main(){
scanf("%d%d%d",&n,&m,&k);
sq=sqrt(n);
for(int j=1;j<=n;j++)
scanf("%d",&a[j]);
for(int j=1;j<=m;j++){
scanf("%d%d",&p[j].l,&p[j].r);
p[j].x=j;
}
sort(p+1,p+m+1,cmp);
int ll=1,rr=1,ss=1;
cnt[a[1]]=1;
for(int j=1;j<=m;j++){
while(rr<p[j].r){
rr++;
ss-=cnt[a[rr]]*cnt[a[rr]];
cnt[a[rr]]++;
ss+=cnt[a[rr]]*cnt[a[rr]];
}
while(ll>p[j].l){
ll--;
ss-=cnt[a[ll]]*cnt[a[ll]];
cnt[a[ll]]++;
ss+=cnt[a[ll]]*cnt[a[ll]];
}
while(rr>p[j].r){
ss-=cnt[a[rr]]*cnt[a[rr]];
cnt[a[rr]]--;
ss+=cnt[a[rr]]*cnt[a[rr]];
rr--;
}
while(ll<p[j].l){
ss-=cnt[a[ll]]*cnt[a[ll]];
cnt[a[ll]]--;
ss+=cnt[a[ll]]*cnt[a[ll]];
ll++;
}
p[j].ans=ss;
}
sort(p+1,p+m+1,cmpp);
for(int j=1;j<=m;j++)
printf("%d
",p[j].ans);
return 0;
}