区間の最値を解く-RMQ-STアルゴリズムの紹介
3137 ワード
解析
STアルゴリズムはRMQ(Range Minimum/Maximum Query)の古典的なアルゴリズムであり、生まれながらにして区間の最値を求めるために使われているが、最値を維持することはできない.つまり、過程で区間のある要素の値を変えることはできない.O(nlogn)の前処理とO(1)のクエリは,大量のクエリが必要なシーンに非常に適している.次に,STアルゴリズムの処理手順を詳細に説明する.
たとえば、10の長さの配列があります.
STアルゴリズムがdp配列を前処理しているため,素朴な線形ルックアップ,複雑度O(n)を用いる場合,STアルゴリズムはO(1)の時間複雑度しか必要としない[1,7]間の最大値をクエリする.
iからの2^j個の数の最値をdp[i][j]で表し、dp[i][j]の「管轄」index=iからの2^j個の数を表すと、どの区間も2つのdp要素に管轄されることは明らかである.例えば上記の[1,7]は、dp[1][2]とdp[4][2]に管轄され、max(dp[1][2]、dp[4][2])は[1,7]の最値である.
どのようにしてdp[1][2]とdp[4][2]の2つの要素を得ますか?簡単に、dp[1][n](2^n<=区間個数)のnをできるだけ大きくして最初の要素を得ることで、2番目の要素を推定することができ、2つの要素の管轄範囲の大きさは同じである.
これにより、dp配列を1つ前に処理するだけでよいが、この前処理は動的に計画されたプロセスであり、移行方程式は次のようになっている.
一方,dp配列の前処理とRMQの解法はちょうど逆過程である.
実戦
POJにはSTアルゴリズムのテンプレート問題Balanced Lineupがあり、2つの配列を前処理するだけで、1つは最大値を表し、もう1つは最小値を表す.
完全なコード:
Javascriptテンプレート:
STアルゴリズムはRMQ(Range Minimum/Maximum Query)の古典的なアルゴリズムであり、生まれながらにして区間の最値を求めるために使われているが、最値を維持することはできない.つまり、過程で区間のある要素の値を変えることはできない.O(nlogn)の前処理とO(1)のクエリは,大量のクエリが必要なシーンに非常に適している.次に,STアルゴリズムの処理手順を詳細に説明する.
たとえば、10の長さの配列があります.
1 3 2 4 9 5 6 7 8 0
STアルゴリズムがdp配列を前処理しているため,素朴な線形ルックアップ,複雑度O(n)を用いる場合,STアルゴリズムはO(1)の時間複雑度しか必要としない[1,7]間の最大値をクエリする.
iからの2^j個の数の最値をdp[i][j]で表し、dp[i][j]の「管轄」index=iからの2^j個の数を表すと、どの区間も2つのdp要素に管轄されることは明らかである.例えば上記の[1,7]は、dp[1][2]とdp[4][2]に管轄され、max(dp[1][2]、dp[4][2])は[1,7]の最値である.
どのようにしてdp[1][2]とdp[4][2]の2つの要素を得ますか?簡単に、dp[1][n](2^n<=区間個数)のnをできるだけ大きくして最初の要素を得ることで、2番目の要素を推定することができ、2つの要素の管轄範囲の大きさは同じである.
これにより、dp配列を1つ前に処理するだけでよいが、この前処理は動的に計画されたプロセスであり、移行方程式は次のようになっている.
dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
一方,dp配列の前処理とRMQの解法はちょうど逆過程である.
実戦
POJにはSTアルゴリズムのテンプレート問題Balanced Lineupがあり、2つの配列を前処理するだけで、1つは最大値を表し、もう1つは最小値を表す.
完全なコード:
#include
#include
#include
#include
using namespace std;
#define N 50005
int maxn[N][32], minn[N][32];
int a[N];
void ST(int n) {
for (int i = 1; i <= n; i++)
maxn[i][0] = minn[i][0] = a[i];
int k = log(n * 1.0) / log(2.0);
for (int j = 1; j <= k; j++)
for (int i = 1; i <= n; i++) {
if (i + (1 << j) - 1 > n) break;
maxn[i][j] = max(maxn[i][j - 1], maxn[i + (1 << (j - 1))][j - 1]);
minn[i][j] = min(minn[i][j - 1], minn[i + (1 << (j - 1))][j - 1]);
}
}
int getAns(int x, int y) {
int k = log(y - x + 1.0) / log(2.0);
return max(maxn[x][k], maxn[y + 1 - (1 << k)][k]) - min(minn[x][k], minn[y + 1 - (1 << k)][k]);
}
int main() {
int n, cas;
scanf("%d%d", &n, &cas);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
ST(n);
while (cas--) {
int x, y;
scanf("%d%d", &x, &y);
printf("%d
", getAns(x, y));
}
return 0;
}
Javascriptテンプレート:
var G = {
dp: [], // dp[i][j] index=i 2^j
init: function(a) {
var n = a.length;
for (var i = 0; i < n; i++)
G.dp[i] = [], G.dp[i][0] = a[i];
var k = ~~(Math.log(n) / Math.log(2));
for (var j = 1; j <= k; j++)
for (var i = 0; i < n; i++) {
if (i + (1 << j) - 1 >= n) break;
// , Math.min()
G.dp[i][j] = Math.max(G.dp[i][j - 1], G.dp[i + (1 << (j - 1))][j - 1]);
}
},
getAns: function(x, y) {
var k = ~~(Math.log(y - x + 1) / Math.log(2));
// , Math.min()
return Math.max(G.dp[x][k], G.dp[y + 1 - (1 << k)][k]);
}
};
var a = [1, 3, 2, 4, 8, 7, 6, 5, 9, 0] // RMQ
G.init(a);
// test cases
for (var i = 0; i < 10; i++)
for (var j = i + 1; j < 10; j++) {
var tmp = a.slice(i, j + 1)
, normalAns = Math.max.apply(null, tmp)
, stAns = G.getAns(i, j);
if (normalAns !== stAns)
console.log('Algorithm went wrong!');
}