Codeforces Round #852 (Div. 2) F. Rebrending(树状数组+动态开点线段树)

题目

n(n<=3e5)个数,第i个数ai(1<=ai<=n),ai两两不同

q(q<=1e6)次询问,每次给出l,r(l<r),

询问[l,r]区间内,值域最接近的两个数,其值域距离是多少

即求l<=s<=t<=r,min(abs(a[s]-a[t]))的值

原题链接

Codeforces Round #397 by Kaspersky Lab and Barcelona Bootcamp (Div. 1 + Div. 2 combined) F. Souvenirs

思路来源

题解 CF765F 【Souvenirs】 - lhm_ 的博客 - 洛谷博客

题解

考虑将询问离线,固定右端点i,只考虑满足 j<i,a_j>a_i的答案,

做一遍之后,再将a_i 的值域翻转(a_{i}=n+1-a_{i})后再做一遍就能得到所有情况。

对于某个右端点r,询问就是在树状数组上查询,

左端点l及l以后的每个位置j的答案的最小值,

所以每个位置j直接维护diff答案,树状数组上求后缀最小值

权值线段树查满足j<i,a_j>a_i的最大位置j

所以,维护动态开点的权值线段树,

下标[1,n],维护的是a值,只查[ai,n]这一段区间的

值域[1,n],维护的是位置,查询某一段区间的最大位置

但一个一个往前跳找出所有的位置j,复杂度无法接受,还需进一步优化。

考虑当前位置为j,要想下一次找到的位置 j' 能更新答案,

其必须满足a_{j'}-a_i<a_j-a_{j'}

因为,点对 (j',j)也会包含在[j',i]的区间中,

所以,a_j-a_{j'}也更新过这个区间询问的答案

所以,要更新后缀最小值就必须满足这个式子。
移项得 a_{j'}-a_i<\frac{1}{2}(a_j-a_i),发现差值每次减半,

具体来说,若a[pos]-a[i]为偶数,则除以2减1,

若为奇数直接除以2下取整,可以统一写做(a[pos]-a[i]-1)/2。

设值域为 V,因此在限制下往前跳的合法位置个数为 O(\log V)

复杂度为 O(n \log^2 V)。 

代码

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10,M=1e6+10;
struct BitSuf{
	static const int N=3e5+10,INF=0x3f3f3f3f;
	int n,tr[N];
	void init(int _n){
		n=_n;
		memset(tr,INF,(n+1)*sizeof(*tr));
	}
	void add(int x,int v){
		for(int i=x;i;i-=i&-i)
		tr[i]=min(tr[i],v);
	}
	int ask(int x){
		int ans=INF; 
		for(int i=x;i<=n;i+=i&-i)
		ans=min(ans,tr[i]);
		return ans;
	}
}tr;
struct DynamicSegmentTree{
	static const int N=3e5+10;
	int cnt;
	struct node{
		int l,r,mx;
		void init(){l=r=mx=0;}
		node(){init();} 
	}e[N*40];
	void init(){
		e[0].init();
		cnt=0;
	}
	DynamicSegmentTree(){init();}
	//1e5开*40,1e6开*45
	//其实开(log(maxn)*(n+m)*maxn)就好
	void newNode(int &cur){
		if(!cur){
			cur=++cnt;
			e[cnt].init();
		}
	}
	void upd(int l,int r,int &cur,int pos,int v){
		newNode(cur);
		e[cur].mx=max(e[cur].mx,v);
		if(l==r)return;
		int mid=(l+r)/2;
		if(pos<=mid)upd(l,mid,e[cur].l,pos,v);
		else upd(mid+1,r,e[cur].r,pos,v); 
	}
	int ask(int l,int r,int cur,int ql,int qr){
		if(!cur)return 0;
		if(ql<=l&&r<=qr)return e[cur].mx;
		int ans=0,mid=(l+r)/2;
		if(ql<=mid)ans=max(ans,ask(l,mid,e[cur].l,ql,qr));
		if(qr>mid)ans=max(ans,ask(mid+1,r,e[cur].r,ql,qr));
		return ans;
	}
}tr2;
int n,m,l,r,a[N],ans[M];
vector<tuple<int,int> >q[N];
void sol(){
	tr.init(n);
	tr2.init();
	int rt=0;
	for(int i=1;i<=n;++i){
		int pos=tr2.ask(1,n,rt,a[i],n);
		while(pos){
			tr.add(pos,a[pos]-a[i]);
			pos=tr2.ask(1,n,rt,a[i],(a[i]+a[pos]-1)/2);
		}
		tr2.upd(1,n,rt,a[i],i);
		for(auto &[l,id]:q[i]){ //后缀min
			ans[id]=min(ans[id],tr.ask(l));
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	tr.init(n);
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=m;++i){
		scanf("%d%d",&l,&r);
		q[r].push_back({l,i});
		ans[i]=n;
	}
	sol();
	for(int i=1;i<=n;++i){
		a[i]=n+1-a[i];
	}
	sol();
	for(int i=1;i<=m;++i){
		printf("%d\n",ans[i]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/129002491