【題解】codeforces765F Souvenirs

題目鏈接

題意:給定一個長度爲n的數組與m個查詢。第i個查詢要求輸出數組下標區間[li,ri]內的數之間的距離(差的絕對值)的最小值。

分析:離線處理查詢。將查詢區間按右端點從小到大排序,維護一個數據結構,使得將數組元素a1,a2,...,ai插入到數據結構之後能夠快速查詢以ai爲右端點的查詢區間的答案。考慮ai對查詢區間的貢獻,設d=|ai-aj|(j<i),則對於滿足l≤j,r=a[i]的查詢區間[l,r],有查詢區間[l,r]的答案≤d。因此可以用線段樹來維護答案,線段樹每個結點記錄左端點處於對應範圍內時(當右端點 爲ai時)的查詢區間的上確界。

        若暴力處理ai對查詢區間的貢獻,則時間複雜度爲O(n),這顯然是不可接受的。不妨先設a1,a2,...,ai-1均大於等於ai(一般情況下處理2次即可),則在計算完元素對(ai,ai-1)對查詢區間的貢獻後,設下一個選取的元素對爲(ai,aj),則aj應滿足aj≤(ai+ai-1)/2。這是因爲:1.aj≥ai-1時元素對(ai,aj)不優於元素對(ai,ai-1)的貢獻;2.(ai+ai-1)/2≤aj≤ai-1時元素對(ai,aj)的貢獻不優於元素對(ai-1,aj)的貢獻。依次類推,計算ai對查詢區間的貢獻時間複雜度可以優化到O(lgn)。

        下面討論程序的實現。我們需要一個數據結構,使得能夠快速求出數組下標在[1,j-1]範圍內大小在範圍[ai,(ai+aj)/2]內的下標最大的數。可以建一棵線段樹,線段樹的每個結點[l,r]存一個vector,vector裏將al,al+1,...,ar按從小到大排序,然後就可以O(lgn^2)地完成這件事(官方題解似乎有O(lgn)的做法)。

代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
struct que
{
	int l,r,i;
	bool operator < (const que &o) const
	{
		return r<o.r;
	}
}qu[maxn];
int n,m,a[maxn],ans[maxn];
vector<int> vec[maxn*4];
int minv[maxn*4],ql,qr,p,val;
void build(int o,int l,int r)
{
	if (l==r) {vec[o].push_back(a[l]);return ;}
	int mid=(l+r)/2;
	build(2*o,l,mid);build(2*o+1,mid+1,r);
	int i=0,j=0;
	while (i<vec[2*o].size()&&j<vec[2*o+1].size())
	{
		if (vec[2*o][i]<=vec[2*o+1][j]) vec[o].push_back(vec[2*o][i]),i++;
		else vec[o].push_back(vec[2*o+1][j]),j++;
	}
	while (i<vec[2*o].size()) vec[o].push_back(vec[2*o][i]),i++;
	while (j<vec[2*o+1].size()) vec[o].push_back(vec[2*o+1][j]),j++;
}
int find(int o,int l,int r,int mine,int maxe)
{
	if (ql<=l&&r<=qr)
	{
		vector<int>::iterator it=lower_bound(vec[o].begin(),vec[o].end(),mine);
		if (it==vec[o].end()||*it>maxe) return -1;
		if (l==r) return l;
		int mid=(l+r)/2,ret=find(2*o+1,mid+1,r,mine,maxe);
		if (ret!=-1) return ret;
		return find(2*o,l,mid,mine,maxe);
	}
	int mid=(l+r)/2,ret=-1;
	if (qr>mid) ret=find(2*o+1,mid+1,r,mine,maxe);
	if (ret!=-1) return ret;
	return find(2*o,l,mid,mine,maxe);
}
void updata(int o,int l,int r)
{
	if (l==r) {minv[o]=min(minv[o],val);return ;}
	int mid=(l+r)/2;
	if (p<=mid) updata(2*o,l,mid);
	else updata(2*o+1,mid+1,r);
	minv[o]=min(minv[2*o],minv[2*o+1]);
}
int query(int o,int l,int r)
{
	if (ql<=l&&r<=qr) return minv[o];
	int mid=(l+r)/2,ret=1e9;
	if (ql<=mid) ret=min(ret,query(2*o,l,mid));
	if (qr>mid) ret=min(ret,query(2*o+1,mid+1,r));
	return ret;
}
int main()
{
	cin>>n;for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	build(1,1,n);
	cin>>m;for (int i=1;i<=m;i++) scanf("%d%d",&qu[i].l,&qu[i].r),qu[i].i=i;
	sort(qu+1,qu+m+1);
	for (int i=0;i<maxn*4;i++) minv[i]=1e9;
	int pos=1;
	for (int i=2;i<=n;i++)
	{
		int j;
		ql=1;qr=i-1;
		j=find(1,1,n,a[i],1e9);
		while (j!=-1)
		{
			int d=a[j]-a[i];
			p=j;val=d;
			updata(1,1,n);
			if (!d||j==1) break;
			d/=2;
			ql=1;qr=j-1;
			j=find(1,1,n,a[i],a[i]+d);
		}
		ql=1;qr=i-1;
		j=find(1,1,n,0,a[i]);
		while (j!=-1)
		{
			int d=a[i]-a[j];
			p=j;val=d;
			updata(1,1,n);
			if (!d||j==1) break;
			d/=2;
			ql=1;qr=j-1;
			j=find(1,1,n,a[i]-d,a[i]);
		}
		while (pos<=m&&qu[pos].r==i)
		{
			ql=qu[pos].l;qr=i;
			ans[qu[pos].i]=query(1,1,n);
			pos++;
		}
	}
	for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章