Different Integers
题意:给你一个数组,每一次询问一个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;
}