2019-2020 ICPC Asia Hong Kong. H. Hold the Line(线段树 二分)

题目链接

这个数据范围有毒。。
这种题记得用fread。


(Description)
(n)个位置,最初每个位置上没有数。
(m)次操作,操作有两种:

  1. 在一个没有数的位置(x)加一个数(v)
  2. 给定(x),询问([l,r])中与(x)相差最小的数,即(min_{vin[l,r]}|x-v|)

(nleq 5 imes10^5, mleq 10^6)

(Solution)
最简单的是线段树套set,(O(mlog^2n))+大常数所以过不了。
Sol 1
考虑离线,把询问按(r)排序,每次用在(r)位置插入的值更新,然后求以(r)为右端点的询问。

设此时的一个询问为([L,R],x),询问编号为(id)
设已经插入的值为(v_i),对应的操作编号为(i),插入位置为(pos_ileq R)
(v_i)能更新询问当且仅当(pos_igeq L)(ilt id)

将询问拆成,找最大的(v_i)满足(v_ileq x)(pos_igeq L)(ilt id),和找最小的(v_i)满足(v_igeq x)(pos_igeq L)(ilt id)
对第一种询问,只需考虑([1,x])中,最靠右的满足(pos_igeq L)(ilt id)(i)是哪个。不妨考虑用一个线段树,对每个节点([l,r]),判断(lleq v_ileq r)时是否有符合条件的(i),如果有就继续先找右儿子再找左儿子,如果该区间没有就return。
也就是用一个线段树维护所有当前的(v_i),节点([l,r])保存所有(lleq v_ileq r)(i)
然后如何判断某节点的(v_i)中是否存在(i)使(pos_igeq L)(ilt id)
注意到我们是按(pos)从小到大更新插入的值,所以后插入的值(pos)一定大于之前的。而节点中有必要保留的点,是要么(pos)更大、要么(i)更小的点。所以如果在某节点插入(v_i)时,(i)小于该节点中另一个值的操作编号,那个值就可以删掉。
容易发现这样每个节点都是一个单调队列,从队首到队尾,编号(i)递增,(pos)递增。
所以每个节点的查询,只需要二分第一个(posgeq L)的值,然后判断其编号(i)是否有(ilt id)(或最后一个(ilt id)的值,然后判断是否其(posgeq L))。
这样每次查询复杂度(O(log^2n))。每次插入值,就更新根节点到该值整条链上的单调队列,复杂度(O(log n))

所以复杂度(O(nlog n+mlog^2n))
注意空间也是(O(nlog n))的,刚开始要建树,根据每个节点上数的个数(前缀和算)分配一下其单调队列的空间。

Sol 2
在线的话可以分块。每个块因为最多有(sqrt n)个数,所以在每次插入时,暴力将这个数放到对应位置,使每块里的数保持有序。
查询时,边界暴力,整块中因为块内有序,可以二分第一个小于等于(x)的数(A[p])(A[p],A[p+1])即为可能答案。
复杂度(O(msqrt n+msqrt nlog n))。和std跑的差不多快。。(反正比我快/kk)


//2292ms	116300KB
#include <bits/stdc++.h>
#define pc putchar
#define gc() getchar()
#define MAXIN 500000
//#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define pb emplace_back
typedef long long LL;
const int N=5e5+5,M=1e6+6,Q=1e7+1e6+5/*队列最少的空间,1e7还是太小*/;

int A[M],sum[M],Ans[M],qid[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Quries
{
	int L,pos,val;
}q[M];
std::vector<int> vec[N];
struct Segment_Tree
{
	#define S M<<2	//值域为M!
	#define ls rt<<1
	#define rs rt<<1|1
	#define lson l,m,ls
	#define rson m+1,r,rs
	int now,res,qL,qid,pos[M],L[S],R[S],q[Q];
	#undef S
	void Build(int l,int r,int rt)
	{
		L[rt]=now, R[rt]=now-1;	//初始化该节点的队首和队尾 
		now+=sum[r]-sum[l-1];	//分配该节点的空间 
		if(l==r)
		{
			pos[l]=rt;			//记一下每个值叶节点的位置 
			return;
		}
		int m=l+r>>1;
		Build(lson), Build(rson);
	}
	void Update(int p,int id,int val)
	{
		for(int rt=pos[val]; rt; rt>>=1)	//记录叶节点位置,自底向上for就可以了 
		{
			int h=L[rt], t=R[rt];
			while(h<=t && q[t]>id) --t;
			q[++t]=id, R[rt]=t;
		}
	}
	bool Check(int rt)
	{
		int l=L[rt],r=R[rt],mid;
		while(l<=r)
		{
			mid=l+r>>1;
			if(q[mid]<qid && ::q[q[mid]].pos>=qL) return 1;
			if(q[mid]<qid) l=mid+1;
			else r=mid-1;
		}
		return 0;
//		while(l<r)
//		{
//			mid=l+r+1>>1;
//			if(q[mid]<qid) l=mid;
//			else r=mid-1;
//		}
//		return q[l]<qid && ::q[q[l]].pos>=qL;
	}
	void QueryL(int l,int r,int rt,int qR)
	{
		if(res||L[rt]>R[rt]) return;	//! 区间里没有数记得return 
		if(r<=qR)
		{
			if(!Check(rt)) return;	//先找到对应区间,再Check,少Check一些是不是会快 
			if(l==r) {res=l; return;}	//当然不合法区间不会DFS到叶节点 
		}
		int m=l+r>>1;
		if(m<qR) QueryL(rson,qR);
		QueryL(lson,qR);
	}
	void QueryR(int l,int r,int rt,int qL)
	{
		if(res||L[rt]>R[rt]) return;	//! 区间里没有数记得return 
		if(qL<=l)
		{
			if(!Check(rt)) return;
			if(l==r) {res=l; return;}
		}
		int m=l+r>>1;
		if(qL<=m) QueryR(lson,qL);
		QueryR(rson,qL);
	}
}T;

inline int read()
{
	int now=0,f=1; char c=gc();
	for(;!isdigit(c);c=='-'&&(f=-1),c=gc());
	for(;isdigit(c);now=now*10+c-48,c=gc());
	return now*f;
}
void Query(int id,int cnt)
{
	int L=q[id].L,val=q[id].val,ans=2e9;
	T.qL=L, T.qid=id;

	T.res=0, T.QueryL(1,cnt,1,val);
	if(T.res) ans=A[val]-A[T.res];
	T.res=0, T.QueryR(1,cnt,1,val);
	if(T.res) ans=std::min(ans,A[T.res]-A[val]);

	Ans[id]=ans==2e9?-1:ans;
}

int main()
{
	for(int Ts=read(); Ts--; )
	{
		int n=read(),m=read(),cnt=m;
		for(int i=1; i<=m; ++i)
		{
			if(!read()) q[i]=Quries{0,read(),read()};
			else q[i]=Quries{read(),read(),read()};
			A[i]=q[i].val;
		}
		std::sort(A+1,A+1+m), cnt=std::unique(A+1,A+1+m)-A-1;	//值的个数为m不是n! 

		memset(sum,0,cnt+1<<2), memset(qid,0,n+1<<2);
		for(int i=1; i<=n; ++i) std::vector<int>().swap(vec[i]);

		for(int i=1; i<=m; ++i)
		{
			int v=std::lower_bound(A+1,A+1+cnt,q[i].val)-A;
			q[i].val=v;
			if(!q[i].L) qid[q[i].pos]=i, ++sum[v];
			else vec[q[i].pos].pb(i);
		}
		for(int i=1; i<=cnt; ++i) sum[i]+=sum[i-1];	//注意n,m,cnt含义!

		#define S 1,cnt,1
		T.now=1, T.Build(S);

		for(int i=1; i<=n; ++i)	//枚举R 
		{
			if(qid[i]) T.Update(i,qid[i],q[qid[i]].val);
			for(auto v:vec[i]) Query(v,cnt);
		}
		
		for(int i=1; i<=m; ++i) q[i].L && printf("%d
",Ans[i]);
	}

	return 0;
}
------------------------------------------------------------------------------------------------------------------------
无心插柳柳成荫才是美丽
有哪种美好会来自于刻意
这一生波澜壮阔或是不惊都没问题
只愿你能够拥抱那种美丽
------------------------------------------------------------------------------------------------------------------------
原文地址:https://www.cnblogs.com/SovietPower/p/14730756.html