Different Integers 牛客网暑期ACM多校训练营(第一场)(树状数组+思维)

Different Integers

牛客网暑期ACM多校训练营(第一场)

题意:给你一个数组,每一次询问一个l和r,需要求[1,l],[r,n]区间有多少个不同的数字

题目链接

官方题解:

思路:将这个数组接在他自己的末尾生成一个新的数组,即num[i+n]=num[i],这样查询[1,l],[r,n]区间有多少个不同的数字就可以转换成查询[r,l+n]区间有多少个不同的数字的问题。记录pre[i]表示区间[1,i]之间有多少个不同的数字,如果我们要查询[l,r]区间的不同数字个数,可以转换成pre[r]-pre[l-1]+区间[1,l-1]和区间[l,r]相同数字的种类个数。比如说:


数组:1 1 2 3 1 2 1
查询[2,4]之间的不同数字的个数
pre[i]={1 ,1 ,2 ,3, 3 ,3, 3 ,3 ,3 ,3, 3, 3, 3 ,3}

pre[4]-pre[2]=2;

但是1在[1,1]区间出现,也在[2,4]区间出现了,减去[1,1]的同时会影响到后面的区间的数字种类个数,所以要把区间[1,l-1]和区间[l,r]重复的数字的种类个数加回来。

所以每查询一个一个区间的[l,r]之间的种类个数,需要求出区间[1,l-1]和区间[l,r]重复的数字的种类个数。

这里我们需要用到树状数组进行单点更新和求区间和。

首先,我们需要预处理出每一个点的数字它的下一次出现的位置,记录在nxt[]数组中。比如说

数组:1 1 2 3 1 2 1

扩展后:1 1 2 3 1 2 1  1 1 2 3 1 2 1

nxt[]={2,5,6,1,7,10, 8, 9, 12, 13 ,0 ,14, 0, 0};

我们这里用离线的做法,保证最后每一个点只处理一遍。将所有查询l和r,按照l的值进行排序,对于每一次的查询,先将[1,l-1]没添加的点添加进来,其实添加的时候是将每一个点的下一次出现位置pre[i]添加进树状数组相应的下标位置,值设置为1。然后查询[l,r]之间的和,区间[l,r]和为多少,就意味着区间[l,r]之间有多少个1,即区间[1,l-1]有多少个不同数字在区间[l,r]之间,这样就可以在O(logn)时间复杂度内查询到区间[1,l-1]和区间[l,r]重复的数字的种类个数。在下一次查询时,又需要将l右移经过的数字的下一次出现的位置pre[i]加入树状数组中。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn=200000+10;
const int inf=0x3f3f3f3f;
#define ll long long
struct P {
	int l,r;
	int id;
	int ans;
} p[2*maxn];
bool cmd(P a,P b) {
	return a.l<b.l;
}
bool cmd1(P a,P b) {
	return a.id<b.id;
}
/********树状数组*/
int lowbit(int x) {
	return x&(-x);
}
int c[maxn];
int sum(int i) {
	int s=0;
	while(i>0) {
		s+=c[i];
		i-=lowbit(i);
	}
	return s;
}
void add(int i,int val,int n) {
	if(i==0)
		return;
	while(i<=n) {
		c[i]+=val;
		i+=lowbit(i);
	}
	return;
}
int query_sum(int l,int r){  //求树状数组区间[l,r]的和 
	return sum(r)-sum(l-1);
}
/*********/ 
int n,m;
int pre[2*maxn];     //pre[i]表示区间[1,i]之间有多少个不同的数字 
int last[maxn],nxt[maxn];  //last为辅助数组,为了求nxt[],nxt[]表示每一个点的数值下一次出现的位置 
int num[2*maxn];      //输入的数组 
bool vis[maxn];       //辅助数组 
void init(int n) {    //预处理出pre[]和nxt[] 
	memset(vis,0,sizeof(vis));
	memset(pre,0,sizeof(pre));
	memset(last,-1,sizeof(last));
	memset(nxt,0,sizeof(nxt));
	for(int i=1; i<=n; i++) {
		if(!vis[num[i]]) {
			pre[i]=pre[i-1]+1;
			vis[num[i]]=true;
		}
		else
		pre[i]=pre[i-1];
	}
	for(int i=1; i<=n; i++) {
		if(last[num[i]]==-1) {
			last[num[i]]=i;
		} else {
			int u=last[num[i]];
			nxt[u]=i;
			last[num[i]]=i;
		}
	}
	return;
}
void solve(int n,int m) {  //求m次询问的结果 
	memset(c,0,sizeof(c));
	int top=0;
	for(int i=1; i<=m; i++) {
		int l=p[i].l;
		int r=p[i].r;
		for(top; top<l; top++) {
			add(nxt[top],1,2*n);
		}
		int u=sum(r);
		int v=sum(l-1);
		p[i].ans=pre[r]-pre[l-1]+query_sum(l,r);
	}
	return;
}
void debug(int n){
	for(int i=1;i<=2*n;i++){
		printf("%d%c",pre[i],i==2*n?'\n':' ');
	}
	for(int i=1;i<=2*n;i++){
		printf("%d%c",nxt[i],i==2*n?'\n':' ');
	}
	for(int i=1;i<=m;i++){
		printf("%d %d\n",p[i].l,p[i].r);
	}
}
int main() {
	while(~scanf("%d %d",&n,&m)) {
		for(int i=1; i<=n; i++) {
			scanf("%d",&num[i]);
			num[i+n]=num[i];
		}
		init(2*n);
		int l,r;
		for(int i=1; i<=m; i++) {    //查询[1,l],[r,n]区间有多少个不同的数字转换成查询[r,l+n]区间有多少个不同的数字的问题
			scanf("%d %d",&l,&r);
			p[i].l=r;
			p[i].r=l+n;
			p[i].id=i;
		}
		sort(p+1,p+m+1,cmd);         //离线处理 
		solve(n,m);
		sort(p+1,p+m+1,cmd1);
		for(int i=1; i<=m; i++) {
			printf("%d\n",p[i].ans);
		}
		//debug(n);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40160605/article/details/81140055