Official
D - お弁当の選択 / Choosing a Lunch Box Editorial by admin
GPT 5.2 High概要
お弁当の総数 \(M = A \times B \times C\) 種類から、区別される \(N\) 人に重複なく割り当てる方法数(順列数)を、\(10^9+7\) で割った余りで求めます。
考察
- お弁当は「(メイン, サイド, デザート)」の \(3\) つ組で、独立に選べるので総数は
\(M = A \times B \times C\) です。 - 「どの2人も同じお弁当にならない」= \(N\) 人に 異なる \(N\) 個 を割り当てる必要があります。
- さらに「社員は区別する」ので、同じ \(N\) 個を選んでも誰にどれを渡すかが違えば別カウントです。
つまり、求めるのは組合せではなく 順列(注入的な割り当て)です。
よって方法数は [ M \times (M-1) \times (M-2) \times \cdots \times (M-N+1) ] という 降順の積(順列 \(P(M,N)\)) になります。
素朴にやると何が困るか
- \(M\) は最大で \(10^6 \times 10^6 \times 10^6 = 10^{18}\) になり得ます。
- 例えば \(M!\) を作って \(\frac{M!}{(M-N)!}\) の形で計算しようとしても、\(M\) が大きすぎて階乗の前計算が不可能です(時間・メモリともに無理)。
- 一方で \(N \le 10^6\) なので、必要な掛け算は \(N\) 回だけ で十分です。
このため、\(P(M,N)\) をそのまま \(N\) 項だけ掛けていくのが最適です。
(例)\(A=2,B=1,C=2\) なら \(M=4\)。\(N=3\) のとき
[
P(4,3)=4 \times 3 \times 2 = 24
]
通り。
アルゴリズム
- \(M = A \times B \times C\) を計算する。
- 答えを \(ans=1\) とする。
- \(i=0\) から \(N-1\) まで順に、\(ans \leftarrow ans \times (M-i)\) を行い、その都度 \(MOD=10^9+7\) で割った余りを取る。
- 実装では各項を \((M-i) \bmod MOD\) にして掛ければよいので、
term = M % MODから始めてterm -= 1していく形で計算できる。 termが負になったらterm += MODして \((M-i) \bmod MOD\) を保つ。
- 実装では各項を \((M-i) \bmod MOD\) にして掛ければよいので、
これで [ ans \equiv \prod_{i=0}^{N-1} (M-i) \pmod{MOD} ] が得られます。
計算量
- 時間計算量: \(O(N)\)(掛け算を \(N\) 回)
- 空間計算量: \(O(1)\)(定数個の変数のみ)
実装のポイント
\(M=A\times B\times C\) は最大 \(10^{18}\) ですが、Python の
intなら安全に扱えます。各項は \((M-i)\) をそのまま巨大整数で持ち続けてもよいですが、毎回
MODを取ることで値の肥大化を防げます。termを減らしていくとき、負になった場合はterm += MODして正の剰余に戻すのが安全です。ソースコード
import sys
MOD = 1_000_000_007
def main():
N, A, B, C = map(int, sys.stdin.readline().split())
M = A * B * C
term = M % MOD
ans = 1
for _ in range(N):
ans = (ans * term) % MOD
term -= 1
if term < 0:
term += MOD
print(ans)
if __name__ == "__main__":
main()
この解説は gpt-5.2-high によって生成されました。
posted:
last update: