「CF522D Closest Equals」

题目大意

给出一个序列,每次查询一段区间内距离最近的相同元素之间的距离.

分析

RMQ+二分的做法好妙啊,我就根本想不出来.

考虑一个数对只有当两个数相等且都在区间内才可能会产生贡献.

记录一个 \(pre_i\) 表示第 \(i\) 个数相同的数上一次出现的位置

这个东西就很像区间取 \(\min\) 了,将 \(i\)\(pre_i\) 的贡献放在 \(pre_i\) 的位置,那么这个区间查询的答案就是 \(l\sim r\) 中记录的贡献的最小值了.

可是直接这样搞可能会导致 \(i\) 在区间外 \(pre_i\) 在区间内的情况出现,那么就考虑将查询离线下来,按右端点排序,只有当 \(i\leq right\) 时才将贡献放在 \(pre_i\) 上.

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=5e5+5;
const int INF=1e9;
int n,m;
int arr[MAXN];
struct Sor
{
	int num,id;
}sor[MAXN];
bool Cmp(Sor a,Sor b)
{
	return a.num>b.num;
}
struct SegmentTree//一颗维护区间 min 的线段树
{
	int min;
}sgt[MAXN*4];
#define LSON (now<<1)
#define RSON (now<<1|1)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
#define NOW now_left,now_right
void PushUp(int now)
{
	sgt[now].min=min(sgt[LSON].min,sgt[RSON].min);
}
void Build(int now=1,int left=1,int right=n)
{
	if(left==right)
	{
		sgt[now].min=INF;
		return;
	}
	Build(LEFT);
	Build(RIGHT);
	PushUp(now);
}
void Updata(int place,int num,int now=1,int left=1,int right=n)//单点修改
{
	if(right<place||place<left)
	{
		return;
	}
	if(left==right)
	{
		sgt[now].min=num;
		return;
	}
	Updata(place,num,LEFT);
	Updata(place,num,RIGHT);
	PushUp(now);
}
int Query(int now_left,int now_right,int now=1,int left=1,int right=n)//区间查询 min
{
	if(now_right<left||right<now_left)
	{
		return INF;
	}
	if(now_left<=left&&right<=now_right)
	{
		return sgt[now].min;
	}
	return min(Query(NOW,LEFT),Query(NOW,RIGHT));
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
#undef NOW
int pre[MAXN];
int last[MAXN];
int answer[MAXN];
struct Range
{
	int left,right,id;
}range[MAXN];
bool Cmp2(Range a,Range b)
{
	return a.right<b.right;
}
int main()
{
	scanf("%d%d",&n,&m);
	REP(i,1,n)//离散化
	{
		scanf("%d",&sor[i].num);
		sor[i].id=i;
	}
	sort(sor+1,sor+1+n,Cmp);
	sor[0].num=sor[1].num-1;
	int tot=0;
	REP(i,1,n)
	{
		if(sor[i].num^sor[i-1].num)
		{
			++tot;
		}
		arr[sor[i].id]=tot;
	}
	REP(i,1,n)//处理 pre 数组
	{
		pre[i]=last[arr[i]];
		last[arr[i]]=i;
	}
	REP(i,1,m)
	{
		scanf("%d%d",&range[i].left,&range[i].right);
		range[i].id=i;
	}
	sort(range+1,range+1+m,Cmp2);//将查询的区间按右端点排序
	int now=1;
	Build();
	REP(i,1,m)
	{
		while(now<=range[i].right)//将小于右端点的贡献放上
		{
			if(pre[now])
			{
				Updata(pre[now],now-pre[now]);
			}
			++now;
		}
		answer[range[i].id]=Query(range[i].left,range[i].right);
		if(answer[range[i].id]==INF)
		{
			answer[range[i].id]=-1;
		}
	}
	REP(i,1,m)//输出
	{
		printf("%d\n",answer[i]);
	}
	return 0;
}

这个东西怎么搞在线 \(\mathcal{O}(m\log n)\)

建可持久化线段树就好了.

posted @ 2020-06-14 19:25  SxyLimit  阅读(171)  评论(0编辑  收藏  举报