Skip to content

Commit 4905fe6

Browse files
committed
atcoder B13 - Supermarket 2 しゃくとり法 (Two Pointers)
1 parent 51acccf commit 4905fe6

9 files changed

Lines changed: 996 additions & 0 deletions

File tree

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// 以下は、**RSA復号処理 + ASCIIコード展開(28ビット → 7ビット×4分割)** を行うJavaScriptコードです。
2+
// `fs` モジュールで標準入力から値を取得し、**実行時間・メモリの消費を抑えた実装**になっています。
3+
4+
// ---
5+
6+
// ## ✅ 処理概要
7+
8+
// 1. 入力:`n`, `e`, `E`
9+
// 2. φ(n) を求めるために、`n` を素因数分解 → `p`, `q` を得る
10+
// 3. `φ(n) = (p-1)(q-1)` を計算
11+
// 4. `d = e⁻¹ mod φ(n)` を求める(拡張ユークリッド)
12+
// 5. `M = E^d mod n`(繰り返し二乗法)
13+
// 6. `M` を28ビットで見て、7ビットごとに区切って文字列化
14+
15+
// ---
16+
17+
// ## ✅ コード(Node.js)
18+
19+
const fs = require('fs');
20+
21+
// 入力取得
22+
const input = fs.readFileSync(0, 'utf8').trim();
23+
const [nRaw, eRaw, Eraw] = input.split(' ').map(BigInt);
24+
25+
const n = nRaw;
26+
const e = eRaw;
27+
const E = Eraw;
28+
29+
// 素因数分解(p, q)
30+
// n は 10^10 以下なので √n まで試せば良い
31+
function findFactors(n) {
32+
const sqrtN = BigInt(Math.floor(Math.sqrt(Number(n))));
33+
for (let i = 2n; i <= sqrtN; i++) {
34+
if (n % i === 0n) {
35+
return [i, n / i];
36+
}
37+
}
38+
return [1n, n]; // fallback
39+
}
40+
41+
// 拡張ユークリッド互除法で逆元を求める
42+
function modInv(a, m) {
43+
let m0 = m, x0 = 0n, x1 = 1n;
44+
while (a > 1n) {
45+
const q = a / m;
46+
[a, m] = [m, a % m];
47+
[x0, x1] = [x1 - q * x0, x0];
48+
}
49+
return x1 < 0n ? x1 + m0 : x1;
50+
}
51+
52+
// 繰り返し二乗法で a^b % m を高速計算
53+
function modPow(base, exp, mod) {
54+
let result = 1n;
55+
base = base % mod;
56+
while (exp > 0n) {
57+
if (exp % 2n === 1n) {
58+
result = (result * base) % mod;
59+
}
60+
base = (base * base) % mod;
61+
exp = exp / 2n;
62+
}
63+
return result;
64+
}
65+
66+
// 1. 素因数分解
67+
const [p, q] = findFactors(n);
68+
69+
// 2. φ(n) = (p-1)(q-1)
70+
const phi = (p - 1n) * (q - 1n);
71+
72+
// 3. 秘密鍵 d = e^-1 mod φ(n)
73+
const d = modInv(e, phi);
74+
75+
// 4. 復号処理 M = E^d mod n
76+
const M = modPow(E, d, n);
77+
78+
// 5. 28ビットを 7ビット × 4 に分割し ASCII 変換
79+
const chars = [];
80+
let temp = M;
81+
for (let i = 0; i < 4; i++) {
82+
const charCode = Number(temp & 0b1111111n); // 下位7ビット
83+
if (charCode !== 0) {
84+
chars.unshift(String.fromCharCode(charCode));
85+
}
86+
temp >>= 7n;
87+
}
88+
89+
// 出力
90+
console.log(chars.join(''));
91+
92+
// ## ✅ 実行例
93+
94+
// ### 入力:
95+
96+
// ```
97+
// 3995747143 3007 602607029
98+
// ```
99+
100+
// ### 出力:
101+
102+
// ```
103+
// PAIZ
104+
// ```
105+
106+
// ---
107+
108+
// ## ✅ 実行時間・メモリ分析
109+
110+
// | ステップ | 時間計算量 | 備考 |
111+
// | --------------- | ----------- | ------------------------- |
112+
// | 素因数分解 | O(√n) | n ≤ 10^10 なので最大 10^5 回ループ |
113+
// | 逆元計算(modInv) | O(log φ(n)) | log(10^10) ≈ 33 回以下 |
114+
// | 繰り返し二乗法(modPow) | O(log d) | 非常に高速 |
115+
// | ビット操作&変換 | O(1) | 最大4文字分 |
116+
// | **総メモリ使用量** | 数百 KB 程度 | BigInt 計算のために多少のオーバーヘッドあり |
117+
118+
// ---
119+
120+
// ## ✅ 補足
121+
122+
// * 暗号文(`E`)が 28 ビットの復号文 `M` を生成する前提です。
123+
// * `M` は ASCII コードの7ビット(0〜127)範囲の文字しか使わない設計です。
124+
125+
// ---
126+
127+
// ご希望があれば、RSAの暗号化側(E = M^e mod n)コードや、多文字列対応の拡張版なども提供可能です。お気軽にどうぞ!
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from typing import Tuple
2+
3+
def extgcd(a: int, b: int) -> Tuple[int, int, int]:
4+
if b != 0:
5+
c, y, x = extgcd(b, a % b)
6+
y -= (a // b) * x
7+
return c, x, y
8+
return a, 1, 0
9+
10+
def modpow(a: int, b: int, m: int) -> int:
11+
ans = 1
12+
while 0 < b:
13+
if b & 1 == 1:
14+
ans = (ans * a) % m
15+
a = (a * a) % m
16+
b >>= 1
17+
return ans
18+
19+
def divisor(n: int) -> Tuple[int, int]:
20+
for i in range(2, int(n ** (1 / 2))):
21+
if n % i == 0:
22+
p = i
23+
q = n // i
24+
return p, q
25+
raise ValueError("No divisors found")
26+
27+
n, e, E = map(int, input().split())
28+
p, q = divisor(n)
29+
n_prime = (p - 1) * (q - 1)
30+
31+
c, x, y = extgcd(e, n_prime)
32+
d = (x + n_prime) % n_prime
33+
message = modpow(E, d, n) # Renamed from M to message
34+
35+
letter = [0] * 4
36+
for i in range(4):
37+
for j in range(7):
38+
if message % 2 == 1:
39+
letter[i] += pow(2, j)
40+
message //= 2
41+
42+
output = ""
43+
for i in range(4):
44+
if letter[3 - i] != 0:
45+
output += chr(letter[3 - i])
46+
print(output)
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
2+
---
3+
4+
## 🔐 問題の背景と目的
5+
6+
RSA暗号では以下の数式に基づいて **秘密の数字 M** を安全にやりとりできます。
7+
8+
* 暗号化:`E ≡ M^e mod n`
9+
* 復号化:`M ≡ E^d mod n`
10+
11+
さらに今回は、`M`(28ビット)を **7ビットごとに区切ってASCII文字に復元**します。
12+
13+
---
14+
15+
## 🧩 入力例
16+
17+
```
18+
n = 3995747143
19+
e = 3007
20+
E = 602607029
21+
```
22+
23+
---
24+
25+
## 🪜 処理ステップ図解(全体)
26+
27+
```mermaid
28+
flowchart TD
29+
A[入力: n, e, E] --> B[素因数分解 n = p × q]
30+
B --> C[φ(n) = (p-1)(q-1)]
31+
C --> D[d = e⁻¹ mod φ(n)]
32+
D --> E[M = E^d mod n]
33+
E --> F[28ビット → 7ビット×4 に分割]
34+
F --> G[ASCIIコード → 文字列]
35+
```
36+
37+
---
38+
39+
## 🔹 ステップ①:n の素因数分解(p, q)
40+
41+
```
42+
n = 3995747143 = p × q
43+
```
44+
45+
調べると、
46+
47+
```
48+
p = 59359
49+
q = 67309
50+
```
51+
52+
#### 図:
53+
54+
```
55+
+-----------------------------+
56+
| 素因数分解 |
57+
+-----------------------------+
58+
| n = pq |
59+
| p = 59359 , q = 67309 |
60+
+-----------------------------+
61+
```
62+
63+
---
64+
65+
## 🔹 ステップ②:φ(n) の計算
66+
67+
```
68+
φ(n) = (p - 1) × (q - 1)
69+
= 59358 × 67308 = 3995620776
70+
```
71+
72+
#### 図:
73+
74+
```
75+
+--------------------------------+
76+
| オイラー関数 φ(n) |
77+
+--------------------------------+
78+
| φ(n) = (p - 1)(q - 1) |
79+
| = 59358 × 67308 |
80+
| = 3995620776 |
81+
+--------------------------------+
82+
```
83+
84+
---
85+
86+
## 🔹 ステップ③:秘密鍵 d の計算
87+
88+
秘密鍵 `d` は以下を満たす逆元:
89+
90+
```
91+
d ≡ e⁻¹ mod φ(n)
92+
```
93+
94+
`e = 3007``φ(n) = 3995620776` に対して、
95+
96+
```
97+
d = 1899220895
98+
```
99+
100+
これは拡張ユークリッド互除法で求めます。
101+
102+
#### 図:
103+
104+
```
105+
+---------------------------------------------------+
106+
| 秘密鍵 d の計算(逆元) |
107+
+---------------------------------------------------+
108+
| e × d ≡ 1 mod φ(n) |
109+
| → 3007 × d ≡ 1 mod 3995620776 |
110+
| → d = 1899220895 |
111+
+---------------------------------------------------+
112+
```
113+
114+
---
115+
116+
## 🔹 ステップ④:復号 `M = E^d mod n`
117+
118+
与えられた `E = 602607029` を復号します:
119+
120+
```
121+
M = E^d mod n
122+
= 602607029^1899220895 mod 3995747143
123+
= 1347701416
124+
```
125+
126+
(繰り返し二乗法を使って効率的に計算)
127+
128+
#### 図:
129+
130+
```
131+
+-----------------------------------------+
132+
| 復号処理:M = E^d mod n |
133+
+-----------------------------------------+
134+
| E = 602607029 |
135+
| d = 1899220895 |
136+
| n = 3995747143 |
137+
| → M = 1347701416 |
138+
+-----------------------------------------+
139+
```
140+
141+
---
142+
143+
## 🔹 ステップ⑤:28ビット → 7ビット × 4 分割
144+
145+
28ビット表現の `M = 1347701416` は 2進数で:
146+
147+
```
148+
M = 01010000 01000001 01001001 01011010
149+
P A I Z
150+
```
151+
152+
上位から7ビットずつ分割:
153+
154+
| S\[0] | S\[1] | S\[2] | S\[3] |
155+
| ----- | ----- | ----- | ----- |
156+
| 80 | 65 | 73 | 90 |
157+
| 'P' | 'A' | 'I' | 'Z' |
158+
159+
#### 図:
160+
161+
```
162+
+-----------------------------------------------------+
163+
| 28ビット分解: 1347701416 |
164+
+-----------------------------------------------------+
165+
| 2進数:01010000 01000001 01001001 01011010 |
166+
| ^ ^ ^ ^ |
167+
| S[0] S[1] S[2] S[3] |
168+
| 80 65 73 90 |
169+
| 'P' 'A' 'I' 'Z' |
170+
+-----------------------------------------------------+
171+
```
172+
173+
---
174+
175+
## ✅ 最終出力
176+
177+
```txt
178+
PAIZ
179+
```
180+
181+
---
182+
183+
## 🧠 処理全体まとめ(再掲)
184+
185+
```mermaid
186+
graph LR
187+
A1[n, e, E 入力] --> A2[素因数分解 → p, q]
188+
A2 --> A3[φ(n) 計算]
189+
A3 --> A4[秘密鍵 d 計算(mod逆元)]
190+
A4 --> A5[復号 M = E^d mod n]
191+
A5 --> A6[28ビットを 7ビット × 4 分割]
192+
A6 --> A7[ASCIIコード → 文字列]
193+
A7 --> A8[出力:PAIZ]
194+
```
195+
196+
---

0 commit comments

Comments
 (0)