离线+线段树/树状数组

最近做了一些线段树+离线的题,总结一下,方便日后再看。

HH的项链

给定一个序列,每次询问一个区间中有多少种不同的数(相同只算一次)。

首先定义一个数组\(pre_i\)表示跟\(a_i\)相同的上一个数的位置
每次对于一个询问[L,R],我们要统计有多少个i满足
\(L<=i<=R\)并且\(pre_i<L\)
但实际上我们可以先分别求出
\([1,R]\), \([1,L-1]\)各自有多少个\(pre_i<L\)相减即可,而后者的答案显然为L-1
所以我们关心的是在\([1,R]\)中有多少\(pre_i<L\)
我们将询问先离线,全部挂在询问的右端点上,建立一颗以pre为下标的树状数组,每次在prei的位置+1即可。

#include<cstdio>
#include<algorithm>
#include<vector>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
using namespace std;
const int N=1e6+5;
int c[N],ans,s;
int n,m,x,op,p[N],t[N],a[N],f[N],g[N];
int l[N],r[N];
vector<int> q[N],id[N];
int lowbit(int x){
	return x&(-x);
}
void add(int x,int y){
	x++;
	while (x<N) {
		c[x]+=y;
		x+=lowbit(x);
	}
}
int ask(int x){
	x++;
	s=0;
	while (x){
		s+=c[x];
		x-=lowbit(x);
	}
	return s;
}
int main(){
//	freopen("data.in","r",stdin);
	
	scanf("%d",&n);
	fo(i,1,n) scanf("%d",&a[i]);
	
	fo(i,1,n) {
		p[i]=t[a[i]];
		t[a[i]]=i;
	}

	scanf("%d",&m);
	fo(i,1,m){
		scanf("%d %d",&l[i],&r[i]);
		q[r[i]].push_back(l[i]-1);
		id[r[i]].push_back(i);
	}
	
	fo(i,1,n){
		add(p[i],1);
		f[i]=i;
		
		for (int j=0;j<(int)q[i].size();j++) {
			g[id[i][j]]=ask(q[i][j]);
		}
	}
	
	fo(i,1,m) {
		ans=g[i]-f[l[i]-1];
		printf("%d\n",ans);
	}
	return 0;
}

cf522D

给定一个序列,每次询问一个区间内相等的数的最小距离是多少,若没有输出-1
跟上一题很像,也是先处理pre,同时处理一个跟pre的距离,因为答案显然是这些当中的一个
那么我们求的就是最小的i-prei
其中i满足
\(L<=prei<=i<=R\)
实际上只用满足两个不等号
\(L<=prei\)
\(i<=R\)
因为\(prei<=i\)
建立一颗以prei为下标的线段树,维护i-prei的最小值
类似的,我们将询问挂在右端点,从左往右扫,这样就满足了\(i<=R\),然后每次更新就给线段树上prei的位置更新为i-prei,然后查询\([L,R]\)的最小值

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define lc o<<1 
#define rc (o<<1)|1
using namespace std;
const int N=1e6+5;
int c[N],ans,s;
int n,m,op,p[N],t[N],a[N],d[N],b[N],zz;
int u[N];
int l[N],r[N],x,y;
int mn[N*4];
vector<int> q[N],id[N];
void update(int o,int l,int r){
	if (l==r) {
		mn[o]=y; return;
	}
	int m=(l+r)>>1;
	if (x<=m) update(lc,l,m);
	else update(rc,m+1,r);
	
	mn[o]=min(mn[lc],mn[rc]);
}
void query(int o,int l,int r){
	
	if (x<=l && r<=y){
		ans=min(ans,mn[o]);  return;
	}
	int m=(l+r)>>1;
	
	if (x<=m) query(lc,l,m);
	if (m<y) query(rc,m+1,r);
}
int main(){
	

//	freopen("data.in","r",stdin);

	memset(mn,0x3f,sizeof(mn));

	scanf("%d %d",&n,&m);
	fo(i,1,n) scanf("%d",&a[i]),b[i]=a[i];
	
	sort(b+1,b+n+1);
	zz=unique(b+1,b+n+1)-(b+1);
	fo(i,1,n) a[i]=lower_bound(b+1,b+zz+1,a[i])-b;
	
	
	fo(i,1,n) {
		p[i]=t[a[i]];
		t[a[i]]=i;
		d[i]=i-p[i];
	}

	fo(i,1,m){
		scanf("%d %d",&l[i],&r[i]);
		q[r[i]].push_back(l[i]);
		id[r[i]].push_back(i);
	}
	
	fo(i,1,n){
		if (p[i]) {
			x=p[i]; y=d[i];
			update(1,1,n);
		}
		for (int j=0;j<(int)q[i].size();j++){
			x=q[i][j]; y=i; ans=1e9;
			query(1,1,n);
			if (ans==1e9) u[id[i][j]]=-1;
			else u[id[i][j]]=ans;
		}
	}
	
	fo(i,1,m) printf("%d\n",u[i]);
	return 0;
}
 

cf1000f

给定一个序列,每次询问一个区间中只出现一次的数,输出任意一个,不存在输出0。
每次对于一个询问[L,R],我们要看是否存在i满足
\(L<=i<=R\)并且\(pre_i<L\),但是这个条件还不完全,只考虑了前面,后面仍然可能有相同的数。
我们还是先离线,将询问挂在右端点,我们注意到这样一个性质,如果当前区间的右端点到了i,那么prei这个点对以后的区间都不会再有贡献,我们直接将其删去即可。具体来说我们维护一颗以i为下标的线段树,维护的是prei,每次将prei的位置设为inf,将i的值设为prei,维护区间最小值,查询\([L,R]\)的最小值是否小于L

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define lc o<<1
#define rc (o<<1)|1
using namespace std;
const int N=5e5+5;
int a[N],n,m,x,y,l,r;
int p[N],las[N],ans[N];

vector<int> q[N],id[N];

pair<int,int> c[N*4],k;

void update(int o,int l,int r){
	if (l==r) {
		c[o]=k;
		return;
	}
	int m=(l+r)>>1;
	if (x<=m) update(lc,l,m);
	else update(rc,m+1,r);
	
	c[o]=min(c[lc],c[rc]);
}

void query(int o,int l,int r){
	if (x<=l && r<=y) {
		k=min(k,c[o]);
		return;
	}
	
	int m=(l+r)>>1;
	if (x<=m) query(lc,l,m);
	if (m<y) query(rc,m+1,r);
}
int main(){
	
//	freopen("data.in","r",stdin);
	memset(c,0x3f,sizeof(c));
	
	scanf("%d",&n);
	fo(i,1,n) scanf("%d",&a[i]);
	
	fo(i,1,n) {
		p[i]=las[a[i]];
		las[a[i]]=i;
	}

	scanf("%d",&m);
	fo(i,1,m) {
		scanf("%d %d",&l,&r);
		q[r].push_back(l);
		id[r].push_back(i);
	}
	
	fo(i,1,n) {
		if (p[i]){
			x=p[i]; k={n+1,0};
			update(1,1,n);
		}
		
		x=i; k={p[i],a[i]};
		update(1,1,n);
		
		for (int j=0;j<int(q[i].size());j++) {
			k={n+1,0};
			
			x=q[i][j]; y=i;
			query(1,1,n);
			if (k.first<q[i][j]) ans[id[i][j]]=k.second;
			else ans[id[i][j]]=0;
		}
	}
	fo(i,1,m) printf("%d\n",ans[i]);
	return 0;
} 
posted @ 2023-02-01 11:01  gan_coder  阅读(41)  评论(0)    收藏  举报