NOIP模拟赛 T3区间

题目大意

n n n个数字,第 i i i个数字为 a i a_i ai。有 m m m次询问,每次给出 k i k_i ki个区间,每个区间表示第 l i , j l_{i,j} li,j到第 r i , j r_{i,j} ri,j个数字,求这些区间中一共出现了多少种不同的数字。部分数据强制在线。

时限1s,空间8MB。

输入格式

第一行包括三个整数 n , m , p n,m,p n,m,p p p p 0 0 0 1 1 1表示是否强制在线。

第二行 n n n个正整数,第 i i i个表示 a i a_i ai

接下来依次给出每个询问,每个询问第一行一个正整数,表示 k i k_i ki。接下来 k i k_i ki行,每行两个正整数,分别表示 l i , j l_{i,j} li,j r i , j r_{i,j} ri,j

p = 1 p=1 p=1且这不是第一个询问,则输入的 l i , j l_{i,j} li,j r i , j r_{i,j} ri,j是经过加密的,你需要将这两个数字分别异或上上一个询问的答案,对 n n n取模后再加 1 1 1,两者的较小值为真实的 l i , j l_{i,j} li,j,较大值为真实的 r i , j r_{i,j} ri,j

输出格式

对每个询问输出一行一个整数表示答案。

输入样例

3 2 0
1 2 1
1
1 2
2
1 1
3 3

输出样例

2
1

数据范围

1 ≤ n , m , ∑ k i , a i ≤ 1 0 5 , 1 ≤ l i , j ≤ r i , j ≤ n 1\leq n,m,\sum k_i,a_i\leq 10^5,1\leq l_{i,j}\leq r_{i,j}\leq n 1n,m,ki,ai105,1li,jri,jn


题解

首先,我们考虑 p = 0 p=0 p=0的情况。

我们可以用 b i t s e t bitset bitset来维护每个点是否出现。先把各个区间离线下来,用莫队求出每个区间的 b i t s e t bitset bitset。把每个询问并起来。这样做的时间复杂度为 O ( n n + n m 64 ) O(n\sqrt n+\dfrac{nm}{64}) O(nn +64nm)

空间开不下,我们考虑优化。

既然要用 b i t s e t bitset bitset,那么时间复杂度肯定是要带 n m 64 \dfrac{nm}{64} 64nm的了。我们不妨将每 n 64 \dfrac{n}{64} 64n个元素分一块,对于每次询问,非整块的暴力处理,再预处理整块到整块的 b i t s e t bitset bitset即可。

空间开不下,就对这 64 64 64个块建 S T ST ST表。建 S T ST ST表不用倍增,对每种长度从左到右推一遍即可。

在优化一下常数。只出现过一次的权值,把它们单独求一个前缀和,每次特殊处理即可。这样的话,每个权值至少出现两次,离散化之后权值个数能减少至少一半。

离散化的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn),建 S T ST ST表的时间复杂度为 O ( n log ⁡ 64 ) O(n\log 64) O(nlog64),查询的时间复杂度为 O ( n m 64 + 64 n ) O(\dfrac{nm}{64}+64n) O(64nm+64n),总时间复杂度为 O ( n m 64 ) O(\dfrac{nm}{64}) O(64nm)。因为权值个数减半,所以时间复杂度能降低到 O ( n m 128 ) O(\dfrac{nm}{128}) O(128nm)

空间复杂度为 O ( n log ⁡ 64 ) O(n\log 64) O(nlog64)

这道题有一定的思维难度,可以结合代码帮助理解。

code

#include
#define N 100032
#define K 1563
#define Z 782
using namespace std;
int n,m,p,c1=0,lst,ans,a[N+5],s[1<<16],t[105],c[N+5],sum[N+5];
struct pt{
	unsigned long long z[Z];
	void set(int x){
		z[x>>6]|=1ull<<(x&63);
	}
	void rev(int x){
		z[x>>6]^=1ull<<(x&63);
	}
	int count(){
		int re=0;
		for(int i=0;i<Z;i++){
			re+=s[z[i]>>48]+s[(z[i]>>32)&65535]+s[(z[i]>>16)&65535]+s[z[i]&65535];
		}
		return re;
	}
}v,b[6][70];//按颜色分成64块
struct node{
	int l,r;
}w[N+5];
bool cmp(node ax,node bx){
	if(ax.l!=bx.l) return ax.l<bx.l;
	return ax.r<bx.r;
}
void kuai(int l,int r){
	if(l>r) return;
	int x=t[r-l+1];
	for(int i=0;i<Z;i++){
		v.z[i]|=b[x][l].z[i]|b[x][r-(1<<x)+1].z[i];
	}
}//所有大块处理
void dd(int l,int r){
	if((l-1)/K==(r-1)/K){
		for(int i=l;i<=r;i++) v.set(a[i]);
	}
	else{
		for(int i=l;i<=(l-1)/K*K+K;i++) v.set(a[i]);
		kuai((l-1)/K+1,(r-1)/K-1);
		for(int i=(r-1)/K*K+1;i<=r;i++) v.set(a[i]);
	}
}//分块
int main()
{
	scanf("%d%d%d",&n,&m,&p);
	for(int i=2;i<=64;i++) t[i]=t[i>>1]+1;
	for(int i=1;i<1<<16;i++) s[i]=s[i>>1]+(i&1);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		++c[a[i]];
	}
	for(int i=1;i<=n;i++){
		sum[i]=sum[i-1];
		if(c[a[i]]==1){
			a[i]=0;++sum[i];
		}
	}//若只出现过一次,放到前缀和数组中
	for(int i=1;i<=n;i++){
		if(a[i]) c[++c1]=a[i];
	}
	sort(c+1,c+c1+1);
	c1=unique(c+1,c+c1+1)-c-1;
	for(int i=1;i<=n;i++){
		if(a[i]){
			a[i]=lower_bound(c+1,c+c1+1,a[i])-c;
		}
	}//离散化
	for(int i=0,x=K;i<6;i++,x<<=1){
		memset(v.z,0,sizeof(v.z));
		memset(c,0,sizeof(c));
		for(int j=1;j<=x;j++){
			if(!c[a[j]]) v.rev(a[j]);
			++c[a[j]];
		}
		b[i][0]=v;
		for(int j=K;j+x<=N;j+=K){
			for(int k=0;k<K;k++){
				if(!c[a[j+x-k]]) v.rev(a[j+x-k]);
				++c[a[j+x-k]];
				--c[a[j-k]];
				if(!c[a[j-k]]) v.rev(a[j-k]);
			}
			b[i][j/K]=v;
		}//按位置分成64块
	}//ST表
	for(int o=1,k,l,r;o<=m;o++){
		ans=0;
		memset(v.z,0,sizeof(v.z));
		scanf("%d",&k);
		for(int i=1;i<=k;i++){
			scanf("%d%d",&w[i].l,&w[i].r);
			if(p&&o>1){
				w[i].l=(w[i].l^lst)%n+1;
				w[i].r=(w[i].r^lst)%n+1;
				if(w[i].l>w[i].r) swap(w[i].l,w[i].r);
			}
		}
		sort(w+1,w+k+1,cmp);
		l=w[1].l;r=w[1].r;
		for(int i=2;i<=k;i++){
			if(w[i].l>r+1){
				ans+=sum[r]-sum[l-1];dd(l,r);l=w[i].l;
			}
			r=max(r,w[i].r);
		}
		ans+=sum[r]-sum[l-1];dd(l,r);//合并区间
		ans+=v.count()-(v.z[0]&1);//z[0]的第一个位置是为0的a值,不统计
		lst=ans;
		printf("%d\n",ans);//求答案
	}
	return 0;
}

你可能感兴趣的:(题解,c++,题解)