(Codeforce484E)サインオンフェンス(永続セグメントツリー+二等分線)

トピックリンク:問題-E-コードフォース

質問の意味:最初にn個の数字を与え、次にm個のクエリを与えます。クエリの形式はl、r、wで、これはl番目の数字とr番号。最大値はいくつですか。

連続したw数を選択するたびに最小値が存在するため、質問の意味は少しわかりにくいです。選択肢の数が異なるため、最小値も異なります。必要なのは最大値です。私たちのすべての選択肢の中の最小値の。

分析:この質問にはまだ一定の思考内容があります。まず、答えは与えられたn個の数字のいずれかでなければならないことがわかります。したがって、高さを2つの部分に分割できますが、間隔を空けて実行する必要があるためです。 lからr連続性の条件を直接解決する方法はありません。線分ツリーでの連続性の出現は、明らかに間隔のマージに関連しています。ここで、線分ツリーの間隔のマージの例を共有します:LCIS(線分ツリー+インターバルマージ)_AC__dreamのブログ-CSDNブログ

最初に最初に与えられた数値を高さから高さで並べ替えてから、議長ツリーを作成できます。次に、高さを除算すると、二等分線分ツリーバージョンと同等になります(線分ツリーバージョンが低いほど、 )、最初に線分ツリーの数値を説明します。i番目のバージョンの線分ツリーの要素はすべてh [i]以上です(並べ替え後)(h [i)の場合) ]はj番目の番号であり、i番目のバージョンでjの位置を1としてマークします(i-1番目のバージョンの線分ツリーに対して)。これにより、i番目のバージョンの線分ツリーで1とマークされたすべての位置に値はすべてh[i]以上(前のバージョンでマークされた値を含む数値はh [i]より大きい)であるため、h[i]が次の条件を満たしているかどうかを判断します。 i番目のバージョンの線分に直接相当する二等分線ツリー上のlからrまでの間隔で連続する1の数を照会するだけで十分です。この場所での変換は非常に素晴らしいです。

合計3つの長さ関連変数がセグメントツリーに記録されます。

lenl[id]は左端点から始まる間隔番号付きid 
の連続した1の数を示しますlenr[id]は右端点で終わる間隔番号付きidの連続した
1の数を示しますlenmx[id]は連続した1の数を示しますID1の最大長の番号が付けられた間隔で

線分ツリー間隔のマージの難しさは、腕立て伏せ関数の記述にあります。腕立て伏せ関数の記述について話しましょう。

腕立て伏せの機能は、間隔IDの左右のサブインターバルでlenl、lenr、lenmxの値を知っていることと同等です。これらの値を左右のサブインターバルで使用して、値を更新します。現在の間隔のこれらの変数に対応します。

最初にlenmxを見てみましょう。3つの可能性があります。1つは左のサブインターバルに等しいlenmxであり、もう1つは右のサブインターバルに等しいlenmxです。最後の可能性は2つのインターバル、つまり左のlenrにまたがることです。サブインターバルと右サブインターバルのlenlの合計

lenlに関しては、この場所を分類して議論する必要があります。左のサブインターバルのlenlが左のサブインターバルの長さに等しい場合、現在のインターバルのlenlは左のサブインターバルの長さ+のlenlに等しくなります。右のサブインターバル

lenr更新方法も同様です。右サブインターバルのlenrが右サブインターバルの長さに等しい場合、現在のインターバルのlenrは右サブインターバルの長さ+左サブインターバルの長さに等しくなります。

注意すべきもう1つのポイントは、間隔クエリを実行する場合です。

トラバースされた現在の間隔がたまたまターゲット間隔内にある場合、現在の間隔の最大値を直接返すことができますが、ターゲット間隔が現在の間隔の左右のサブ間隔にまたがる場合、lenr[lnを直接返すことはできません。 [id]] + lenl [rn [id]]これは、これに含まれる連続1がターゲット間隔を超える可能性があるため、これら2つの値に対して特定の処理を実行する必要があるためです。現在の間隔が[l、r]の場合、次に、現在の間隔左のサブ間隔は[l、mid]で、右のサブ間隔は[mid + 1、r]です。次に、[L、mid]と[mid]の連続する1の最大長を照会する必要があります。 +1、R]、言い換えると、lenr [ln [id]]の長さは[L、mid]の区間の長さを超えることはできず、同様にlenl[rn[id]]の長さは[mid + 1、R]の間隔の長さ、これら2つの値の最小値を取る必要があるだけです

これらの手順を使用すると、コードを簡単に記述できます。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1e5+10;
const int M=20; 
int ln[N*M],rn[N*M],root[N],idx;
int lenl[N*M],lenr[N*M],lenmx[N*M];
//lenl[id]表示编号为id的区间以左端点开始连续1的个数 
//lenr[id]表示编号为id的区间以右端点结束连续1的个数
//lenmx[id]表示编号为id的区间中连续1的最大长度
struct node{
	int h,id;
}p[N];
bool cmp(node a,node b)
{
	return a.h>b.h;
}
void pushup(int id,int l,int r)//区间合并 
{
	lenmx[id]=max(lenmx[ln[id]],lenmx[rn[id]]);//当前区间的最长连续1的个数分布在一个区间中 
	lenmx[id]=max(lenmx[id],lenr[ln[id]]+lenl[rn[id]]);//当前区间的最长连续1的个数分布在左右两个子区间中
	int mid=l+r>>1;
	if(lenl[ln[id]]==(mid-l+1))//当前区间左子区间以左端点开始连续1的个数等于左子区间长度,当前区间以左端点开始连续1的个数就应该计算上右子区间以左端点开始连续1的个数 
		lenl[id]=lenl[ln[id]]+lenl[rn[id]];
	else//否则当前区间以左端点开始连续1的个数就是当前区间以左子区间左端点开始连续1的个数 
		lenl[id]=lenl[ln[id]];
	if(lenr[rn[id]]==(r-mid))//当前区间右子区间以右端点结束连续1的个数等于右子区间长度,当前区间以右端点结束连续1的个数就应该计算上左子区间以右端点结束连续1的个数 
		lenr[id]=lenr[rn[id]]+lenr[ln[id]];
	else//否则当前区间以右端点结束连续1的个数就是当前区间以右子区间右端点结束连续1的个数 
		lenr[id]=lenr[rn[id]];
	return ;
}
void update_point(int pre,int id,int pos,int l,int r)
{
	ln[id]=ln[pre];rn[id]=rn[pre];lenl[id]=lenl[pre];lenr[id]=lenr[pre];lenmx[id]=lenmx[pre];
	if(l==r)
	{
		lenl[id]=lenr[id]=lenmx[id]=1;
		return ;
	}
	int mid=l+r>>1;
	if(pos<=mid) ln[id]=++idx,update_point(ln[pre],ln[id],pos,l,mid);
	else rn[id]=++idx,update_point(rn[pre],rn[id],pos,mid+1,r);
	pushup(id,l,r);
}
int query_interval(int id,int L,int R,int l,int r)
{
	if(l>=L&&r<=R) return lenmx[id];
	int mid=l+r>>1;
	int ans=0;
	if(L<=mid) ans=max(ans,query_interval(ln[id],L,R,l,mid));
	if(mid+1<=R) ans=max(ans,query_interval(rn[id],L,R,mid+1,r));
	ans=max(ans,min(lenr[ln[id]],/*左子区间长度*/mid-L+1)+min(lenl[rn[id]],/*右子区间长度*/R-mid));//把区间[L,R]分成两段,但是要保证计算连续1长度时不能包含区间[L,R]之外的部分,所以应该取最小值 
	return ans;
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&p[i].h),p[i].id=i;
	sort(p+1,p+n+1,cmp);//按照高度进行排序 
	for(int i=1;i<=n;i++)
	{
		root[i]=++idx;
		update_point(root[i-1],root[i],p[i].id,1,n);//低版本的线段树中的高度是较高的 
	}
	int m;
	cin>>m;
	for(int i=1;i<=m;i++)
	{
		int l,r,len;
		scanf("%d%d%d",&l,&r,&len);
		int ll=1,rr=n;//二分线段树的版本 
		while(ll<rr)
		{
			int mid=ll+rr>>1;//低版本的线段树中的高度是较高的
			if(query_interval(root[mid],l,r,1,n)>=len) rr=mid;//所以当较高版本线段树中能够满足有连续len个1存在,应该降低线段树版本继续搜索 
			else ll=mid+1;
		}
		printf("%d\n",p[ll].h);//输出最终线段树版本对应插入的数 
	}
	return 0;
}

おすすめ

転載: blog.csdn.net/AC__dream/article/details/123843372