P2709 小B的询问题解 莫队

题意大致是这样的:

给你一个数列a。

只有查询操作,每次查询一个区间。 求出:

其中c[i]表示数字i在区间[l,r]中出现的次数。

容易发现,当c[i]=0的时候我们无需进行累加操作。

使用莫队解决,该莫队中的L和R移动的时候,分两种情况。

1.L增加或者R减少(即区间缩小)对应移动到的数字为x,那么维护的now此时:

要减去值为:x的出现次数 * x的出现次数   - (x的出现次数 -1)*(x的出现次数 -1)

2.L减少或者R增加(即区间扩大)对应移动到的数字为x,那么维护的now此时:

要增加值为:(x的出现次数+1)*(x的出现次数+1)-  x的出现次数 * x的出现次数

AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e6+10;
struct query{
	int l,r,b,id;
}e[N];
int n,m,k,a[N],cnt[N],ans[N];
bool cmp(query a,query b){
	return (a.b^b.b) ? a.b<b.b : (a.b&1 ? a.r<b.r : a.r>b.r);
}
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0' || ch>'9'){if(ch=='-') f=-1;ch=getchar();}while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
void write(int x){if(x<0) putchar('-'),x=-x;if(x>9) write(x/10);putchar(x%10+'0');return;}
signed main(){
	n=read(),m=read(),k=read();
	int len=sqrt(n);
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	for(int i=1;i<=m;i++) {
		e[i].l=read(),e[i].r=read();
		e[i].id=i;
		e[i].b=e[i].l/len;
	}
	sort(e+1,e+1+m,cmp);
	int l=1,r=0,now=0;
	for(int i=1;i<=m;i++){
		int ql=e[i].l,qr=e[i].r;
		while(l<ql){
			if(cnt[a[l]])now-=cnt[a[l]]*cnt[a[l]]-(cnt[a[l]]-1)*(cnt[a[l]]-1);
			--cnt[a[l++]];
		}
		while(l>ql){
			cnt[a[--l]]++;
			if(cnt[a[l]])now+=cnt[a[l]]*cnt[a[l]]-(cnt[a[l]]-1)*(cnt[a[l]]-1);
		} 
		while(r<qr){
			cnt[a[++r]]++;
			if(cnt[a[r]])now+=cnt[a[r]]*cnt[a[r]]-(cnt[a[r]]-1)*(cnt[a[r]]-1);
		} 
		while(r>qr){
			if(cnt[a[r]])now-=cnt[a[r]]*cnt[a[r]]-(cnt[a[r]]-1)*(cnt[a[r]]-1);
			--cnt[a[r--]];
		} 
		ans[e[i].id]=now;
	}
	for(int i=1;i<=m;i++){
		write(ans[i]);puts("");
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值