クエリメニュー【累積和・区間和・二次元累積和・二次元区間和・平方分割のバケット・平方分割】
【STEP: 1】コード
reader.on('close', () => {
const [N, K] = lines[0].split(' ').map(Number);
const A = []; // 入力値
const S = [0]; // A の累積和
for (let i = 0; i < N; i++) {
A.push(Number(lines[i + 1])); // A に 入力値を保存
S.push(S[i] + A[i]); // S に A の累積和を保存
}
for (let i = N + 1; i <= N + K; i++) {
const Q = Number(lines[i]);
console.log(S[Q]); // S[Q] (A[0]〜A[Q-1]の総和) を出力
}
});
S は 配列 A の 累積和 を保存するための配列です。S[i + 1]
の値は S[i] + A[i]
になりました。
【STEP: 2】コード
reader.on('close', () => {
const [N, K] = lines[0].split(' ').map(Number);
const A = [];
const S = [0]; // A の累積和
for (let i = 0; i < N; i++) {
A.push(Number(lines[i + 1])); // A に 入力値を保存
S.push(S[i] + A[i]); // S に A の累積和を保存
}
for (let i = N + 1; i <= N + K; i++) {
const [l, r] = lines[i].split(' ').map(Number);
console.log(S[r] - S[l - 1]); // A[r - 1] 〜 A[l -1] の総和
}
});
【STEP: 1】と同じく S は 配列 A の 累積和 を保存するための配列です。例えば
A[3]
から A[5]
までの各値の和は S[6]-S[3]
でした。【STEP: 3】コード
reader.on('close', () => {
const [H, W, N] = lines[0].split(' ').map(Number);
const A = []; // H ☓ W の2次元配列
const S = [new Array(W + 1).fill(0)]; // A の2次元累積和 (H + 1) ☓ (W + 1)
for (let i = 1; i <= H; i++) {
A.push(lines[i].split(' ').map(Number)); // A に 入力値(配列)を保存
S.push([0]); // S に [0] を追加
}
for (let h = 0; h < H; h++) {
for (let w = 0; w < W; w++) {
S[h + 1][w + 1] = S[h][w + 1] + S[h + 1][w] - S[h][w] + A[h][w];
// 二次元累積和 = 左の値 + 上の値 - 左斜め上の値 + A[h][w]
}
}
for (let i = H + 1; i <= N + H; i++) {
const [y, x] = lines[i].split(' ').map(Number);
console.log(S[y][x]); // y ✕ x (矩形領域)の2次元累積和
}
});
S は ニ次元配列 A の 累積和 を保存するための 二次元配列 です。 S[h + 1][w + 1]
の値は S[h][w + 1] + S[h + 1][w] - S[h][w] + A[h][w]
で求めています。
Task
【STEP: 4】コード
reader.on('close', () => {
const [H, W, N] = lines[0].split(' ').map(Number);
const A = [];
const S = [new Array(W + 1).fill(0)];
for (let i = 1; i <= H; i++) {
A.push(lines[i].split(' ').map(Number));
S.push([0]);
}
for (let h = 0; h < H; h++) {
for (let w = 0; w < W; w++) {
S[h + 1][w + 1] = S[h][w + 1] + S[h + 1][w] - S[h][w] + A[h][w];
}
}
for (let i = H + 1; i <= N + H; i++) {
const [sy, sx, ey, ex] = lines[i].split(' ').map(Number);
console.log(S[ey][ex] - S[sy - 1][ex] - S[ey][sx - 1] + S[sy - 1][sx - 1]);
// 矩形領域の累積和 = 右下(領域内) - 左下の左隣(領域外) - 右上の上(領域外) + 左上の斜め上(領域外)
}
});
【STEP: 3】と同様に 2次元累積和 S を生成し 領域内の 右下 – 左下の左隣 – 右上の真上 +左上の斜め上 で 区間和 を求めることができました。
console.table(S)
で S の値を テーブル形式で出力すると 解りやすいと思います。【STEP: 5】コード
reader.on('close', () => {
const N = 10000;
const R = Math.sqrt(N); // N の平方根
const A = [];
for (let i = 0; i < N; i++) {
A.push(Number(lines[i])); // 入力値を A に保存
}
for (let i = 0; i < R; i++) {
const a = A.splice(0, R); // A の値を先頭から R 個切り取り…
A.push(a); // 切り取った値(配列)を A の末尾へ挿入
}
A.forEach(a => {
console.log(Math.max(...a));
});
});
Math.sqrt(N)
で N ( A の 要素数) の 平方根 (100)を求め Aの値を100ずつ切り取った値の最大値 Math.max(...a)
を求めて出力しました。
【FINAL】コード
reader.on('close', () => {
const N = 10000;
const R = Math.sqrt(N); // N の平方根
const K = Number(lines[0]); // 区間の個数
const A = [];
const M = [];
for (let i = 1; i <= N; i++) {
A.push(Number(lines[i])); // 入力値を A に保存
}
for (let i = 0; i < R; i++) {
const a = A.splice(0, R); // A の値を先頭から R 個切り取り…
A.push(...a); // 切り取った値(配列)を A の末尾へ挿入
M.push(Math.max(...a));
}
for (let n = N + 1; n <= N + K; n++) {
const [l, r] = lines[n].split(' ').map(x => x - 1);
//console.log(Math.max(...A.slice(l, r + 1)));
let ans = A[l];
let i = l;
while (i <= r) {
if (i % R === 0 && i + R - 1 <= r) { // i mod R = 0 且つ i + 99 が 範囲内に収まれば
ans = Math.max(ans, M[i / R]); // ans (最大値)を ans と M[i / R]の大きい方で更新
i += R; // i を i + R (100) で更新
} else { // i mod R != 0 なら…
ans = Math.max(ans, A[i]); // ans (最大値)を ans と A[i]の大きい方で更新
i++;
}
}
console.log(ans);
}
});
【STEP: 5】と同様に配列 M を生成。A の 添字 が R で割り切れて I + 99 が 終点 以下 の場合は ans と M[i / R]
を比較 し 大きい方で ans を上書き。それ以外は ans と A[i]
を比較し 同様に ans を上書き
余談ですが。。
A[l]〜A[r]
までの要素を切り取って最大値を求めてもOKでした。
//console.log(Math.max(...A.slice(l, r + 1)));
コメント