今回は paiza の「名刺バインダー管理」の問題に挑戦!
問題概要
状況
- 名刺は「ファイル」に収められている。
- 1ファイルには
n
個のポケット が横に並んでいて、1ポケットに「表と裏」の2枚の名刺が入っている。 - したがって 1ファイルに 2n枚の名刺 がある。
並び方
- 表:1〜n 番が左から順に並ぶ
- 裏:n+1〜2n 番が左から順に並ぶ
- ただし裏は背中合わせなので、「表の左端 ↔ 裏の右端」がペアになる。
与えられるもの
-
n
(ポケット数) -
m
(調べたい名刺番号)
求めるもの
- 「
m
番の名刺の裏側の番号」
入力例:
3 1 // n m
出力例:
6
✅OK例:
const rl = require('readline').createInterface({ input:process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const [n, m] = lines[0].split(' ').map(Number);
const file = Math.floor((m - 1) / (2 * n));
const pos = (m - 1) % (2 * n);
const pairPos = 2 * n - 1 - pos;
const result = file * 2 * n + pairPos + 1;
console.log(result);
});
全体のアイデア
-
1つのファイルには 表
n
枚 + 裏n
枚 =2n
枚 が入っている。 -
m
がどのファイル(0始まり)にあるかを求め、そのファイル内での位置を見れば、同じポケットの裏側の位置がすぐに求まる。 -
実装は「0始まりに直して考える」のが楽 →
m-1
を使う。
図解で理解する「バインダーの構造」
例:n = 3
の場合
→ 1ファイルには「表 3 枚 + 裏 3 枚 = 6 枚」が入っている
表から見た場合(左→右)
+---+---+---+
| 1| 2| 3|
+---+---+---+
裏から見た場合(左→右)
+---+---+---+
| 4| 5| 6|
+---+---+---+
でも実際は「ポケット」が3つあり、1つのポケットに2枚(表と裏)が背中合わせで入っている。
- ポケット 1: 表=1, 裏=6
- ポケット 2: 表=2, 裏=5
- ポケット 3: 表=3, 裏=4
🔍 コードの詳しい説明
① どのファイルか?
const file = Math.floor((m - 1) / (2 * n));
-
m-1
で 0始まり に変換(例:m=1
→0
)。 -
2*n
ごとにファイルが区切られているので、商を取れば 何番目のファイルか(0始まり)になる。
(1ファイルに 2n 枚入っているので、(m-1)/(2*n)
の商でファイル番号が決まる)
例:n=3
のとき、2*n = 6
。
-
m = 1 ~ 6
はfile = 0
(1枚目のファイル) -
m = 7 ~ 12
はfile = 1
(2枚目のファイル)
② ファイル内の位置
const pos = (m - 1) % (2 * n);
- ファイル内での 相対位置(0〜2n-1) を求める。
- そのファイルの中で左から何番目か(0始まり)
-
0
がそのファイルの先頭(左端の表)、2n-1
がそのファイルの最後(右端の裏)。
例(n=3
):
-
m=1
→pos=0
-
m=3
→pos=2
-
m=4
→pos=3
-
m=6
→pos=5
n=3
なら pos
の範囲は 0〜5
。
図で書くとこう:
pos: 0 1 2 3 4 5 (→ 0 ~ 2n-1)
表: 1 2 3
裏: 4 5 6
③ 裏側の位置
const pairPos = 2 * n - 1 - pos;
-
同じポケットの裏側はファイル内で鏡像の位置にある、という性質を使っている。
-
ファイル内のインデックスを
0 ~ (2n-1)
とすると、「ペアはpos
と2n-1 – pos
」 の組。
-
直感的な考え方:左から
i
番目(0始まり)のポケットの表はpos = i
、同じポケットの裏は右からi
番目に相当するので2n-1 – i
※ 再掲:2n-1 = そのファイルの最後(右端の裏)
例(n=3
):
-
pos=0
のペア →pairPos = 5
(1 ↔ 6) -
pos=1
のペア →pairPos = 4
(2 ↔ 5) -
pos=2
のペア →pairPos = 3
(3 ↔ 4) -
pos=3
のペア →pairPos = 2
(裏側のカードが表側に対応)
④ 全体での番号に戻す
const result = file * 2 * n + pairPos + 1;
-
file * 2 * n
でそのファイルの先頭(1始まりで言うとfile*2n + 1
)に相当するセットを作る。 -
pairPos
を足して ファイル内での位置 を指定し、最後に+1
で 1始まりの番号に戻す。 -
そのファイルの 「基準位置」=
file*2*n
に、裏の位置pairPos
を足して、さらに+1
で1始まりに戻す。 -
つまり「ファイル先頭 + ファイル内の位置 + 1」= 裏側の元の番号。
例(n=3
):
-
m=1
→file=0, pos=0, pairPos=5
→result = 0 + 5 +1 = 6
→ 1番の裏は6番! -
m=4
→file=0, pos=3, pairPos=2
→result = 0 + 2 +1 = 3
-
m=7
→file=1, pos=0, pairPos=5
→result = 6 + 5 +1 = 12
📝 まとめ
-
file
:その名刺が入っているファイル(0始まり) -
pos
:ファイル内での位置(左から 0 .. 2n-1) -
pairPos
:同じポケットの裏側はファイル内で反転した位置 →2n-1-pos
-
result
:そのファイルの先頭にpairPos
を足して 1始まりに戻したもの