【算法】【离线查询】(离散化)(前缀)

5 阅读2分钟

atcoder.jp/contests/ab…

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+5;
const int inf=1e9;
int a[N],b[N],n;
int val[N];
int l[N],r[N];
int pos[N];
int pre[N],suf[N];
bool chk[N];
bool vis[N];

//判断:A的前x个,在b的y中必须出现
//b的前y个不能出现a中没有的数 


//pos[i]记录i在b中第一次出现的位置
//chk[i]//标记i在a中是否第一次出现
//pre[x]a的前x个数在b中最晚的出现位置
//suf[x]a的后x个数在b中出现的最早位置
//mn b中出现最早,在a中没有的数的位置
//对每个x,合法的区间在l[x],r[x] 
signed main(){
	cin>>n;
	int cnt=0;
	for(int i=1;i<=n;i++){cin>>a[i];val[++cnt]=a[i];
	}for(int i=1;i<=n;i++){cin>>b[i];val[++cnt]=b[i];
    }sort(val+1,val+cnt+1);
    int len=unique(val+1,val+cnt+1)-val-1;
    //映射成1-len 
    for(int i=1;i<=n;i++)a[i]=lower_bound(val+1,val+len+1,a[i])-val;
    for(int i=1;i<=n;i++)b[i]=lower_bound(val+1,val+len+1,b[i])-val;
    for(int i=1;i<=n;i++)if(!pos[b[i]])pos[b[i]]=i;//记录v在b中出现的第一个位置
	 for(int i=1;i<=n;i++)if(!vis[a[i]]){
	 vis[a[i]]=i;chk[i]=1;}
	 pre[0]=0;
	 for(int i=1;i<=n;i++){
	 	pre[i]=pre[i-1];;
	 	if(chk[i])pre[i]=max(pre[i],pos[a[i]]==0?n+1:pos[a[i]]);
	 }
	 suf[n+1]=n+1;
	for(int i=n;i>=1;i--){
		suf[i]=suf[i+1];//后面所有数出现的最早位置
		//不能包含后面的数 
		if(chk[i])suf[i]=min(suf[i],pos[a[i]]==0?n+1:pos[a[i]]);
	}
	//在b中出现,在a中未出现的最早位置 
int mn=n+1;
	for(int i=1;i<=n;i++)if(!vis[b[i]])mn=min(mn,i);
	//对每个x,算[l[x],r[x]]
		for(int i=1;i<=n;i++){
		if(!chk[i]){l[i]=l[i-1],r[i]=r[i-1];continue;}//之前的 
		l[i]=pre[i];       // y必须 >= 所有A前x中新数在B中的首次出现位置
		r[i]=min(suf[i+1],mn)-1; // y必须 < 最早出现A没有的数的位置
	}
	int q;
	cin>>q; 
	while(q--){
	int x;int y;cin>>x>>y;
		// 如果y落在 [l[x], r[x]] 里,输出Yes
		puts(l[x]<=y&&y<=r[x]?"Yes":"No");
	}
	 
}

思路

本题使用后缀最小值 suf 来确定查询的合法上限,确保 B 的前 y 项不会出现 A 前 x 项中不存在的数字,从而保证集合完全相等。前缀负责下限,后缀负责上限,二者共同构成合法查询区间,实现离线 O (1) 查询。