間隔最大値クエリ問題の一般的な解決策
間隔の最大値クエリの問題、つまりRMQ(範囲の最小/最大クエリ)
一般的なソリューションには、単純なアルゴリズム-O(n)前処理、O(n)クエリ、および全体的な複雑度O(n + nq)が含まれます
線分ツリー-O(nlogn)前処理、O(logn)クエリ、全体的な複雑度O((n + q)logn)
STアルゴリズム-O(nlogn)前処理、O(1)クエリ、全体的な複雑度O(nlogn + q)
STアルゴリズム
STアルゴリズムは、乗算アルゴリズムと動的プログラミングに基づいて静的間隔 RMQ を解くアルゴリズムです
単純なアルゴリズムを無視して、ラインツリーと比較する
利点は、クエリの時間の複雑さが線形であることであり、これは多くのクエリタイプに有益です。
欠点は、静的間隔のクエリのみをサポートすること(値は初期化後に変更できない)であり、ほとんどの場合、スペースの複雑さはラインセグメントツリーよりも大きい(ラインツリーの保守的なケースはO(4n)、STアルゴリズムはO(nlogn)です。)
STアルゴリズムの実装
前処理セクション
①:乗算アルゴリズムに基づいて、特定の位置iから2番目の位置までの2のべき乗の間隔の最大値を前処理して格納する必要があります。
②:このクエリの間隔にx要素があると仮定すると、アルゴリズムO(1)が実装されていることを確認するために、logxを返して切り捨てる必要があるため、このステップも前処理および保存されます。
クエリセクション
けれども、乗算アルゴリズムの使用
クエリの左境界Lから始めることができると考えるのは簡単です
ログ(R-L + 1)が全体の間隔の最大値に引き下げられるたびに、回答から最大値を取得します(ここでの最大値は前処理部分①です)
左境界Lを更新し、それを続行します
(図の矢印は各Lの更新プロセスです)
また、R-L + 1をバイナリに変換した後、1の位置に基づいて実行するステップ数としても見ることができます
しかし、このアプローチは明らかにO(logn)レベルです
実際、前処理されているため、ある位置iから始まる2の倍数の累乗からの間隔の最大値
その後、我々はでき(R-L + 1)ログ値
Lから右へ、2 ログの長さの間隔の最大値をとります
次に、Rから開始して、左側の 2つのログの長さの間隔で最大値を取ります
これら2つの最大値のうち大きい方が答えであり、時間の複雑さはO(1)です。
撮影した間隔をとって左部にL及び右Rは、ことが保証されていない部分的にオーバーラップしなければならない、そしてそれ以上の他の位置よりも
(図の2つの線分は、取られた間隔を表します)
STアルゴリズムのコード実装
コードでは最大値を例に取り、最小値に変更するには、maxをminに変更するだけです。
データ保存方法
const int MAXN=100050,MAXF=18;
int ar[MAXN];//原数组
int dp[MAXN][MAXF];//dp[i][j]表示从i开始往右2^j长度的区间内的最值
int LOG[MAXN];//LOG[i]=log2(i)向下取整
2つのMAXF-1 > MAXNを確認し、国境を越えないようにする
前処理セクション
dp配列の場合
2 0 = 1であるため、dp[i][0] = ar[i]
レイヤー0が処理されます(他に条件がない場合は、ar配列を無視して直接dp配列に読み込むことができます)
他の問題の乗算アルゴリズムと同様に、j = 1から始めて、dpの考え方によれば、状態遷移方程式は2 i = 2 i-1 + 2 i-1から次のように取得できます。
dp[i][j] = max( dp[i][j-1] , dp[i+(1<<(j-1))][j-1] )
言語の説明は
開始位置からI 2 J区間の最大長は、形成されていてもよい「開始位置から、I 2 、J-1区間の最大長」2から、」I + J-1開始位置2 J-1の長さの間隔内「最大」これら2つの値は、
for(int i=1;i<=n;i++)
dp[i][0]=ar[i];
for(int j=1;(1<<j)<=n;j++)//如果长度已经超过n,则没有继续处理的必要
for(int i=1;i+(1<<(j-1))<=n;i++)//如果从i开始往右没法取2^j长度的区间,则无法继续处理
dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
LOG配列の場合
除算の性質に応じて、ログ配列を渡すことができます
log[i] = log[i/2] + 1
転送済み(ログ[1] = 0)
LOG[1]=0;
for(int i=2;i<=n;i++)
LOG[i]=LOG[i/2]+1;
クエリセクション
クエリ間隔LとRを入力
まず、区間内の要素の数はR-L + 1であり、対応する端の長さは2 LOGです[R-L + 1]
d = LOG [R-L + 1]とします
Lから右への間隔はLを左境界とし、最大値はdp [L] [d]
Rから左への区間の左境界はR-2 d +1であり、最大値はdp [R-(1 << d)+1] [d]です。
2つの値の大きい方が答えです
scanf("%d%d",&L,&R);
d=LOG[R-L+1];
printf("%d\n",max(dp[L][d],dp[R-(1<<d)+1][d]));
完全なプログラム(テンプレート)
テンプレートの質問の例:Luogu P3865
(この質問にTLEがある場合は、速読を使用してみてください。タイトルは説明されています)
#include<bits/stdc++.h>
using namespace std;
const int MAXN=100050,MAXF=18;
int ar[MAXN];
int dp[MAXN][MAXF];
int LOG[MAXN];
int main()
{
int n,q,L,R,d;
scanf("%d%d",&n,&q);
LOG[1]=0;
for(int i=2;i<=n;i++)
LOG[i]=LOG[i/2]+1;
for(int i=1;i<=n;i++)
{
scanf("%d",&ar[i]);
dp[i][0]=ar[i];
}
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<(j-1))<=n;i++)
dp[i][j]=max(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
while(q--)
{
scanf("%d%d",&L,&R);
d=LOG[R-L+1];
printf("%d\n",max(dp[L][d],dp[R-(1<<d)+1][d]));
}
return 0;
}