NOIP2023模拟7联测28 花之舞

题目大意

有一个花园,每朵花可以表示为平面直角坐标系上的 N N N个点,第 i i i个点的坐标为 O ( x i , y i ) O(x_i,y_i) O(xi,yi)。定义两朵花之间的距离为它们的切比雪夫距离,即 d i s ( u , v ) = max ⁡ ( ∣ x u − x v ∣ , ∣ y u − y v ∣ ) dis(u,v)=\max(|x_u-x_v|,|y_u-y_v|) dis(u,v)=max(xuxv,yuyv)

q q q个询问,第 i i i个询问为一个区间 [ l i , r i ] [l_i,r_i] [li,ri],请确定一个 k ( l ≤ k ≤ r ) k(l\leq k\leq r) k(lkr),最大化 min ⁡ 1 ≤ u < v ≤ r , u ≠ k , v ≠ k d i s ( u , v ) \min\limits_{1\leq u1u<vr,u=k,v=kmindis(u,v)。即,最大化:删掉一朵花后,区间里花之间距离最小值。

3 ≤ n ≤ 3 × 1 0 4 , 1 ≤ q ≤ 3 × 1 0 5 , 1 ≤ x i , y i ≤ 1 0 8 3\leq n\leq 3\times 10^4,1\leq q\leq 3\times 10^5,1\leq x_i,y_i\leq 10^8 3n3×104,1q3×105,1xi,yi108

1 ≤ l i < r i ≤ n , r i − l i ≥ 2 1\leq l_i1li<rin,rili2

保证不存在重合的点。

时间限制 4000 m s 4000ms 4000ms,空间限制 1024 M B 1024MB 1024MB


题解

显然,要最大化任意两点的最小值,最近点对的两个点必须删一个。那么,我们可以想办法求出所有在询问中可能用到的最近点对。

枚举 k k k 0 ≤ k ≤ log ⁡ v 0\leq k\leq \log v 0klogv,其中 v v v为值域),将平面直角坐标系分为大小为 2 k × 2 k 2^k\times 2^k 2k×2k的矩阵。对于每个 k k k,我们考虑求所有距离在 [ 2 k − 1 , 2 k ) [2^{k-1},2^k) [2k1,2k)上的点对(当然,求这个的目的是求出所有在询问中可能用到的最近点对,得到距离小于 2 k − 1 2^{k-1} 2k1的点对也无妨)。对于每个矩阵,找到相邻矩阵(相邻指共边或共顶点,也就是八连通)和自己的矩阵,对这九个矩阵分别进行操作。每次操作就是将这两个矩阵(自己和操作对象)的点按编号从小到大排序。这自己的矩阵为 A A A,操作对象的矩阵为 B B B,则对于 A A A中的每个点 x x x,在 B B B中找到最小的点 y y y y ≥ x y\geq x yx(注意 x , y x,y x,y是编号),将 z ∈ [ max ⁡ ( 0 , y − 8 ) , y − 1 ] z\in [\max(0,y-8),y-1] z[max(0,y8),y1]的点都存储在 x x x v e c t o r vector vector中,表示 x x x z z z可能在询问中构成最近点对。

为什么要取 z ∈ [ max ⁡ ( 0 , y − 8 ) , y − 1 ] z\in[\max(0,y-8),y-1] z[max(0,y8),y1]呢?因为如果 B B B中要取到 z ′ z' z,使得 z ′ < max ⁡ ( 0 , y − 8 ) z'<\max(0,y-8) z<max(0,y8) z ′ z' z x x x可能构成最近点对,则能让其构成最近点对的区间可定是包含 [ z ′ , x ] [z',x] [z,x]的。既然当前枚举的是距离在 [ 2 k − 1 , 2 k ) [2^{k-1},2^k) [2k1,2k)上的点对,则如果 B B B中存在 z ∈ [ max ⁡ ( 0 , y − 8 ) , y − 1 ] z\in [\max(0,y-8),y-1] z[max(0,y8),y1] z ′ z' z的距离小于 2 k − 1 2^{k-1} 2k1,则 z z z z ′ z' z的距离一定比 x x x z ′ z' z的距离小,也就是说只要询问包含 x x x z ′ z' z,则最近点对一定是 z z z z ′ z' z,也就是说 x x x z ′ z' z不可能在询问中构成最近点对。

我们发现,在最坏情况下,在 B B B中取 6 6 6个点之后,不管怎么加点,都会使得存在两个点的距离小于 2 k − 1 2^{k-1} 2k1。考虑到后面要删一个点,所以就取到 7 7 7 8 8 8为宜,所以上面取的区间为 [ max ⁡ ( 0 , y − 8 ) , y − 1 ] [\max(0,y-8),y-1] [max(0,y8),y1]。最后还要对每个点的 v e c t o r vector vector去重。

然后我们考虑询问怎么做。我们可以依照询问右边界升序扫描线,将可能的最近点对插入到线段树中。下面称上面处理出来的可能在询问中用到的最近点对为支配点对。

线段树的每个节点只考虑 左端点在节点对应的区间里 且 右端点不超过当前扫描到的边界 的支配点对,维护几个值,分别是:

  • 这些支配点对里,最近点对是哪对,距离是多少
  • 分别删掉上一条中最近点对中的一个点后的最近点对距离

合并区间的时候,设两边最近点对分别是 ( u 1 , v 1 ) (u_1,v_1) (u1,v1) ( u 2 , v 2 ) (u_2,v_2) (u2,v2),其中 u 1 < u 2 u_1u1<u2。如果两边最近点对有共同端点,就可以将 删掉这个共同端点后的最近点对距离 更新为两边最小值;如果两边最近点对没有共同端点,那么一边的最近点对可能更新另一边的删掉某点后的最近点对距离。

询问时考虑删掉最近点对的哪个点,取最优即可。

时间复杂度为 O ( n log ⁡ n log ⁡ v + q log ⁡ n ) O(n\log n\log v+q\log n) O(nlognlogv+qlogn),其中 v v v x i , y i x_i,y_i xi,yi的值域。常数比较大,但时限有 5000 m s 5000ms 5000ms,是可以过的。

code

#include
#define lc k<<1
#define rc k<<1|1
using namespace std;
const int N=30000,Q=300000,inf=2100000000;
int n,q,x[N+5],y[N+5],ans[Q+5];
vector<int>w[N+5];
vector<pair<int,int>>g[N+5];
map<pair<int,int>,vector<int>>mp;
struct node{
	int mn,x,y,v1,v2;
}tr[4*N+5];
int dis(int i,int j){
	return max(abs(x[i]-x[j]),abs(y[i]-y[j]));
}
void build(int k,int l,int r){
	tr[k]=(node){inf,-1,-1,inf,inf};
	if(l==r) return;
	int mid=l+r>>1;
	build(lc,l,mid);build(rc,mid+1,r);
}
node merge(node a,node b){
	node re;
	int v1,v2;
	if(a.mn<b.mn){
		v1=min(a.v1,b.mn);v2=a.v2;
		if(a.y==b.y) v2=min(v2,b.v2);
		else if(a.y==b.x) v2=min(v2,b.v1);
		else v2=min(v2,b.mn);
		re=(node){a.mn,a.x,a.y,v1,v2};
	}
	else{
		v1=b.v1;v2=b.v2;
		if(b.x==a.y) v1=min(v1,a.v2);
		else v1=min(v1,a.mn);
		if(b.y==a.y) v2=min(v2,a.v2);
		else v2=min(v2,a.mn);
		re=(node){b.mn,b.x,b.y,v1,v2};
	}
	return re;
}
void ch(int k,int l,int r,int x,int y){
	if(l==r&&l==x){
		int d=dis(x,y);
		if(d<tr[k].mn) tr[k]=(node){d,x,y,inf,tr[k].mn};
		else if(d<tr[k].v2) tr[k].v2=d;
		return;
	}
	int mid=l+r>>1;
	if(x<=mid) ch(lc,l,mid,x,y);
	else ch(rc,mid+1,r,x,y);
	tr[k]=merge(tr[lc],tr[rc]);
}
node find(int k,int l,int r,int x,int y){
	if(l>=x&&r<=y) return tr[k];
	int mid=l+r>>1;
	if(y<=mid) return find(lc,l,mid,x,y);
	if(x>mid) return find(rc,mid+1,r,x,y);
	return merge(find(lc,l,mid,x,y),find(rc,mid+1,r,x,y));
}
int main()
{
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]);
	for(int dv=0;dv<=27;dv++){
		mp.clear();
		for(int i=1;i<=n;i++){
			mp[{x[i]>>dv,y[i]>>dv}].push_back(i);
		}
		for(auto pv:mp){
			const auto &p=pv.first;
			const auto &v=pv.second;
			for(int dx=-1;dx<=1;dx++){
				for(int dy=-1;dy<=1;dy++){
					auto it=mp.find({p.first+dx,p.second+dy});
					if(it==mp.end()) continue;
					const auto &v1=it->second;
					for(int i=0,j=0;i<v.size();i++){
						while(j<v1.size()&&v1[j]<v[i]) ++j;
						for(int k=max(0,j-8);k<j;k++){
							w[v[i]].push_back(v1[k]);
						}
					}
				}
			}
		}
	}
	for(int i=1;i<=n;i++){
		sort(w[i].begin(),w[i].end());
		w[i].erase(unique(w[i].begin(),w[i].end()),w[i].end());
	}
	scanf("%d",&q);
	for(int i=1,l,r;i<=q;i++){
		scanf("%d%d",&l,&r);
		g[r].push_back({l,i});
	}
	build(1,1,n);
	for(int i=1;i<=n;i++){
		for(auto l:w[i]) ch(1,1,n,l,i);
		for(auto k:g[i]){
			int l=k.first,id=k.second;
			node re=find(1,1,n,l,i);
			ans[id]=max(re.v1,re.v2);
		}
	}
	for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
	return 0;
}

你可能感兴趣的:(题解,好题,题解,c++)