XTCPC2019 湘潭邀请赛 Problem C Chika and Friendly Pairs (莫队+树状数组)

文章来自  Icontofig 's Blog // 我们的征程是星辰大海!Away From OI,Come to ICPC(查看友链请点About Me)

 

题意:

给定n个数,m条询问,每个询问中包含l、r查询这个区间[l,r]中有多少个数对满足两个数的绝对值之差小于等于K

题解:

上来一看区间查询,第一反应一般都是线段树/树状数组等数据结构。

但是看一下这道题要求什么,求区间内满足条件的数对个数。

我好像没有见过能维护数对个数的树状数组或者线段树什么的。

对于数对而言,一般都应该先枚举其中一个数,剩下的才能去判断吧。

如果两个数i,j满足绝对值之差小于等于K,那么肯定能有这样一个关系式:ik<=j<=i+k

这样的话,对于某个区间内的所有数,能跟当前枚举出来的数x组成满足题意的数对的,一定在[x-k,x+k]这个区间里,我们只需要统计这个区间有多少个数就行了,这样每一侧的操作是O(Llogn)(L代表区间长度)的。

但是现在问的是对于某个给定的区间内的所有数能够组成的合法数对个数,我们就不能对区间每个数都像上面这样子暴力去统计了。那要怎么办呢?

答案就是使用莫队算法,里层查找答案时使用树状数组。

使用莫队算法能够使得我们有效利用重叠区间所统计的答案,而不必过多的修改统计树状数组来查询答案。

莫队的基本做法就是:先√n对区间分块,然后按照左端点所属块的序号为第一关键字,右端点大小为第二关键字对询问操作进行排序,然后设置l和r代表当前的位置指针, 枚举询问,不断移动l和r使之与当前的询问的l和r重合,期间添加修改信息。

对于此题,在l,r移动时,区间每加入一个数,先用树状数组统计当前区间内的满足条件的数的个数,然后将此数插入树状数组中。区间中要移除一个数的时候类似,不过两个步骤要反过来一下。读者可以自行理解一下为什么。

此题还有一个关键点是,离散化数x、x-k,x+k所组成的新数组,否则数太大树状数组会开不下的。然后离散化后的标号要O(n)用数组处理,不能使用map记录(map的查询复杂度是O(logn)的,会超时)。

总体复杂度O(n√nlogn)(实际上达不到这么大好像)

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <map>
using namespace std;
const int maxn = 1e5+5;
map<int,int>usp;
int n,m,k;
int c[maxn];
struct qarea{
	int l,r,id;
}q[maxn];
int sum[maxn];
int belong[maxn];
bool cmp(qarea a,qarea b){
	return ((belong[a.l] == belong[b.l]) ? (a.r < b.r) : (a.l < b.l));
}
int a[maxn][3];
int lowbit(int x){
	return (x & -x);
}
void update(int now,int d){
	for(int i = now;i < maxn;i += lowbit(i)){
		c[i] += d;
	}
	return;
}
int query(int now){
	int ret = 0;
	int x = now;
	while(x > 0){
		ret += c[x];
		x -= lowbit(x);
	}
	return ret;
}
int get_num(){
	int num = 0;
	char c;
	bool flag = false;
	while((c = getchar()) == ' ' || c == '\r' || c == '\n');
	if(c == '-')
		flag = true;
	else num = c - '0';
	while(isdigit(c = getchar()))
		num = (num<<3) + (num<<1) + c - '0';
	return (flag ? -1 : 1) * num;
}
struct data{
	int v,id,type;
}b[maxn];
bool cmp2(data x,data y){
	return x.v < y.v;
}
const int INF = 2e9+5;
int main(){
	n = get_num();
	m = get_num();
	k = get_num();
	for(int i = 1;i <= n;++i){
		a[i][1] = get_num();
		b[i].v = a[i][1];b[i].id = i;b[i].type = 1;
		b[i+n].v = b[i].v - k;b[i+n].id = i;b[i+n].type = 0;
		b[i+2*n].v = b[i].v + k;b[i+2*n].id = i;b[i+2*n].type = 2;
	}
	sort(b+1,b+3*n+1,cmp2);
	int cnt = 0;
	int pre = -INF;
	for(int i = 1;i <= 3*n;++i){
		if(b[i].v != pre){
			cnt++;
			pre = b[i].v;
			b[i].v = cnt;
		}
		else b[i].v = cnt;
	}
	for(int i = 1;i <= 3*n;++i){
		a[b[i].id][b[i].type] = b[i].v;
	}
	int pp = sqrt(n);
	for(int i = 1;i <= n;++i){
		belong[i] = i / pp;
	}
	for(int i = 1;i <= m;++i){
		q[i].l = get_num();
		q[i].r = get_num();
		q[i].id = i;
	}
	sort(q+1,q+m+1,cmp);
	int l = 1;
	int r = 0;
	int ans = 0;
	for(int i = 1;i <= m;++i){
		while(q[i].l > l){
			update(a[l][1],-1);
			ans -= (query(a[l][2]) - query(a[l][0]-1));
			l++;
		}
		while(q[i].l < l){
			l--;
			ans += query(a[l][2]) - query(a[l][0]-1);
			update(a[l][1],1);
		}
		while(q[i].r > r){
			r++;
			ans += query(a[r][2]) - query(a[r][0]-1);
			update(a[r][1],1);
		}
		while(q[i].r < r){
			update(a[r][1],-1);
			ans -= (query(a[r][2])-query(a[r][0]-1));
			r--;
		}
		sum[q[i].id] = ans;
	}
	for(int i = 1;i <= m;++i){
		printf("%d\n",sum[i]);
	}
	return 0;
}


Sign in to leave a comment.
No Leanote account ? Sign up now.
0 comments
Title - Artist
0:00