离线+线段树/树状数组
最近做了一些线段树+离线的题,总结一下,方便日后再看。
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;
}