以下、二分探索を用いた JavaScript の実装です。
binarySearch 関数を用いて X の位置を求め、1-based index で出力します。
function binarySearch(arr, x) {
let left = 0,
right = arr.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (arr[mid] === x) {
return mid + 1; // 1-based index
} else if (arr[mid] < x) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 通常ここには到達しない(Xは必ず存在する)
}
function main() {
const fs = require('fs');
const input = fs.readFileSync('/dev/stdin', 'utf8').trim().split('\n');
const [N, X] = input[0].split(' ').map(Number);
const A = input[1].split(' ').map(Number);
console.log(binarySearch(A, X));
}
main();- 二分探索: 配列
Aはソート済みなので、二分探索 (binarySearch) を用いてXの位置を高速に見つける。 - 1-based index: 出力は1-based index のため、
mid + 1を返す。 - 入力処理:
fs.readFileSync("/dev/stdin", "utf8")で標準入力を取得し、改行で分割。
- 二分探索の計算量は O(log N) であり、最大
N=100000でも十分高速。
- 最小の時間: 1秒
- 最大の時間: ( K \times \min(A) )(最速のプリンターがすべて印刷する場合)
- 二分探索 を使い、「ある時間 ( T ) において K 枚以上のチラシが印刷されるか?」を判定する
この判定を効率よく行い、最小の ( T ) を求めます。
以下は 二分探索 を用いた実装です。
function findPrintTime(N, K, A) {
let left = 1,
right = K * Math.min(...A);
const canPrintKSheets = (T) => {
let totalSheets = 0;
for (let i = 0; i < N; i++) {
totalSheets += Math.floor(T / A[i]);
if (totalSheets >= K) return true; // K 枚以上印刷できるならOK
}
return false;
};
while (left < right) {
let mid = Math.floor((left + right) / 2);
if (canPrintKSheets(mid)) {
right = mid; // K 枚以上印刷できるなら、もっと小さい時間を探す
} else {
left = mid + 1; // K 枚印刷できないなら、もっと時間が必要
}
}
console.log(left);
}
// 標準入力の処理
const readline = require('readline');
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
let input = [];
rl.on('line', (line) => {
input.push(line.trim());
}).on('close', () => {
const N = parseInt(input[0]);
const K = parseInt(input[1]);
const A = input[2].split(' ').map(Number);
findPrintTime(N, K, A);
});- 最小時間
left = 1 - 最大時間
right = K * min(A)(最速のプリンターだけが K 枚印刷する場合)
- ある時間
Tで 合計何枚のチラシが印刷されるか を計算 - 各プリンター
iについてMath.floor(T / A[i])を足していく - 合計
K枚以上印刷できればtrue、できなければfalse
mid = (left + right) / 2を計算canPrintKSheets(mid)を評価trueならright = mid(もっと小さい時間を探す)falseならleft = mid + 1(もっと時間が必要)
left == rightになったとき、最小のTが求まる
- 判定関数 は O(N)
- 二分探索 は O(log(K × min(A)))
- 合計計算量 は O(N log K) で、高速に求められます。
3
7
3 5 7
9
- 3 秒ごとのプリンター → 3, 6, 9, ...
- 5 秒ごとのプリンター → 5, 10, ...
- 7 秒ごとのプリンター → 7, 14, ...
- 9秒後に7枚目のチラシが出るので答えは 9。
- 二分探索 を使うと O(N log K) で高速に解ける
- 判定関数 で「時間 T で K 枚印刷できるか?」を判定
- 最小の時間 を求めるため、
right = midで狭めていく
この問題を効率的に解くために、二分探索(Binary Search)または双方向ポインタ(Two Pointers) を活用します。
- ソート不要
既に小さい順に並んでいるため、ソート処理は不要です。 - 二分探索を使用する方法
各A[i]について、A[i] + K以下の最大のjを 二分探索 で求め、i < jとなるペア数を加算する。 - 双方向ポインタを使用する方法
iを左ポインタ、jを右ポインタとして探索A[j] - A[i] > Kならiを進めるA[j] - A[i] ≤ Kならjを進め、ペア数を計算
O(N log N) で解ける。
function countPairsBinarySearch(N, K, A) {
let count = 0;
for (let i = 0; i < N; i++) {
let left = i,
right = N - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (A[mid] - A[i] <= K) {
left = mid + 1;
} else {
right = mid - 1;
}
}
count += right - i;
}
console.log(count);
}
// 入力例
const [N, K, ...A] = `5 3
1 2 4 5 8`
.split(/\s+/)
.map(Number);
countPairsBinarySearch(N, K, A);こちらも O(N) で解ける。
function countPairsTwoPointers(N, K, A) {
let count = 0;
let j = 0;
for (let i = 0; i < N; i++) {
while (j < N && A[j] - A[i] <= K) {
j++;
}
count += j - i - 1;
}
console.log(count);
}
// 入力例
const [N, K, ...A] = `5 3
1 2 4 5 8`
.split(/\s+/)
.map(Number);
countPairsTwoPointers(N, K, A);二分探索:各A[i]に対し、A[i] + K以下の最大のjを探し、O(log N)双方向ポインタ:jを増やしながらA[j] - A[i] <= Kを満たす最大のjを求め、O(N)
O(N log N) よりも O(N) の双方向ポインタの方が高速 なので、基本的には 双方向ポインタ を推奨します。
この問題を解くには、全探索では計算量が O(N^4) = 10^{12} となり、制約 (N \leq 1000) を考えると計算時間的に厳しいです。
そこで、二分探索(またはハッシュセット)を活用して O(N^2 \log N) もしくは O(N^2) に改善する方法を採用します。
- A, B の全てのペアの和を列挙し、リスト
ABを作成する(要素数は (N^2))。 - C, D の全てのペアの和を列挙し、ハッシュセット
CD_setに格納する(検索を O(1) にするため)。 ABの各要素sum_abに対し、K - sum_abがCD_setに存在するか調べる。
これにより、計算量は以下の通り:
ABの生成: (O(N^2))CD_setの生成: (O(N^2))ABの各要素についてCD_setを検索: (O(N^2))
総計: (O(N^2)) であり、N=1000 でも現実的な時間で処理可能!
function canAchieveK(N, K, A, B, C, D) {
let AB = new Set();
// AとBの全ペアの和を計算し、集合ABに格納
for (let i = 0; i < N; i++) {
for (let j = 0; j < N; j++) {
AB.add(A[i] + B[j]);
}
}
// CとDの全ペアの和を計算し、K から引いた値がABに存在するか確認
for (let i = 0; i < N; i++) {
for (let j = 0; j < N; j++) {
if (AB.has(K - (C[i] + D[j]))) {
console.log('Yes');
return;
}
}
}
console.log('No');
}
// 入力例
const N = 3,
K = 50;
const A = [3, 9, 17];
const B = [4, 7, 9];
const C = [10, 20, 30];
const D = [1, 2, 3];
canAchieveK(N, K, A, B, C, D);-
Set(集合)を使って探索を O(1) に
ABのリストをハッシュセットにすることで、CDの各要素を検索する操作を O(1) に短縮。
-
メモリ使用量を最小限に抑える
ABはN^2のサイズなので、N=1000なら10^6個の要素であり許容範囲。
入力
3 50
3 9 17
4 7 9
10 20 30
1 2 3
出力
Yes