【 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;
}
posted @ 2017-02-17 01:56  水郁  阅读(340)  评论(0编辑  收藏  举报
……