0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

平方分割のバケット

0
Posted at

今回は paiza の「平方分割のバケット」問題に挑戦!


問題概要

paiza くんは、数列の特定区間の最大値を効率よく求めたいと考えている。

もし毎回その区間を全て調べる方法を使うと、最大で O(NK) の時間がかかってしまい、クエリ数が多い場合に非常に非効率である。


そこで「平方分割」というアルゴリズム・手法を使い、あらかじめ区間ごとの情報をまとめておくことで、処理を高速化したい。

↓ 平方分割

  1. 長さ N の配列が与えられたとき、N の平方根 x を求め、配列を長さ x の配列に分割し、それぞれの配列について目的の値を調べておく。
    (分割で得られる最後の配列の長さは必ずしも x になるとは限りません)
  2. 調べたい区間に完全に含まれている配列についての 1. で求めた値と、その配列以外の部分の値を全て調べて、目的の値を求める。

この問題では、長さ 10,000 の数列 A について手順 1. を行う。
10,000 の平方根は 100 なので、先頭から 100 要素ずつの最大値を求めて出力せよ。



入力される値

A_1
...
A_10000

期待する出力

ans_1
...
ans_100






✅ OK例:

const rl = require('readline').createInterface( { input:process.stdin });

const arrA = [];

rl.on('line', (input) => arrA.push(Number(input)));


rl.on('close', () => {
    const result = [];
    for(let i = 1; i <= 100; i++){
        const temp = arrA.slice((i - 1) * 100, i * 100);
        result.push(Math.max(...temp));
    }
    console.log(result.join('\n'));
});

  • slice((i – 1) * 100, i * 100)
    例)i=1 のとき → slice(0, 100) → A₁〜A₁₀₀

  • Math.max(…temp):配列の最大値を展開して求める

  • result.join(‘\n’):1行ずつ出力形式に整形



🔍 改めて何をやっているか

長さ 10,000 の配列 arrA を、
→ 100 要素ずつ(√10000 = 100)に区切って
→ 各区間ごとに 最大値 を求めている。






🔎 補足:平方分割って本来どう使う?

この問題は準備(前処理)だけだけど、実際には以下の手順で使う。

  • √N 個の「ブロック」に分ける
  • 各ブロックにあらかじめ最大値などを保存
  • クエリ(範囲の最大など)が来たら:
    • ブロックをまたいでいないなら:そのまま調べる
    • ブロックをまたいでいるなら:
      • 両端は1個ずつ調べる
      • 真ん中は 前処理した最大値を参照 → 高速!




🔧 問題設定(例)

長さ N = 12 の配列 A があるとする:

A = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8]

ここに対して、次のような「区間最大値クエリ」が複数回くるとする:

Q1. A[2..7] の最大値は?  ← 回答:9
Q2. A[5..12] の最大値は? ← 回答:9
Q3. A[1..4] の最大値は?  ← 回答:4



🔍 普通にやると?

各クエリで 全部調べると O(N) × クエリ数(K)

function maxNaive(l, r) {
  let max = -Infinity;
  for (let i = l; i <= r; i++) {
    max = Math.max(max, A[i]);
  }
  return max;
}

→ クエリが多いと遅い!



✅ 平方分割で高速化

🔹① 前処理:√Nごとにブロックを分ける

12の平方根 ≒ 3.46 → 切り上げて 4 にする
配列Aを4つのブロックに分ける:

ブロック0: A[0..2]  = [3, 1, 4] → 最大4
ブロック1: A[3..5]  = [1, 5, 9] → 最大9
ブロック2: A[6..8]  = [2, 6, 5] → 最大6
ブロック3: A[9..11] = [3, 5, 8] → 最大8

→ blockMax = [4, 9, 6, 8]



🔹② クエリ回答の流れ(例:Q2: A[5..12])

端の余り部分(5~5)と(9~12)は手動で調べる
完全に含まれるブロック(6~8)は blockMax[2] を使う!

A[5]        = 9        ← 自分で調べる
block 2     = 6        ← blockMax[2]
A[9..11]    = 3,5,8    ← 自分で調べる
--------------------------
→ max(9, 6, 8) = 9

これでループが最大 √N 回で済む!





🟩 コードスケッチ(簡易)

const A = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8];
const n = A.length;
const blockSize = Math.ceil(Math.sqrt(n));
const blockMax = new Array(blockSize).fill(-Infinity);

// 前処理:ブロック最大値を計算
for (let i = 0; i < n; i++) {
  const b = Math.floor(i / blockSize);
  blockMax[b] = Math.max(blockMax[b], A[i]);
}

// クエリ処理関数
function query(l, r) {  // 0-indexed
  let maxVal = -Infinity;
  while (l <= r && l % blockSize !== 0) maxVal = Math.max(maxVal, A[l++]); // 前半余り
  while (l + blockSize - 1 <= r) {
    maxVal = Math.max(maxVal, blockMax[Math.floor(l / blockSize)]);
    l += blockSize;
  }
  while (l <= r) maxVal = Math.max(maxVal, A[l++]); // 後半余り
  return maxVal;
}




📌 まとめ:平方分割とは

特徴 内容
目的 範囲クエリ(最大/最小/合計など)の高速化
前処理時間 O(N)
1回のクエリ時間 O(√N)
対応できる操作 加算・最大・最小など(可換な操作)




僕の失敗談(´;ω;`)と解決法🐈

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?