AtCoder PR

AtCoder ABC432c「小さな飴と大きな飴」配分問題をやさしく数式化してみる

AtCoder abc432c解説
記事内に商品プロモーションを含む場合があります

AtCodeで緑コーダーを目指して、精進中ですが、
2025年11月15日の問題が少し難しかったのでまとめておきます!
https://atcoder.jp/contests/abc432/tasks/abc432_c
まずは、C問題を安定して解けるレベルを目指したいですね〜

導入:飴を配るだけなのに、なぜこんなに難しい?

この記事では、AtCoder の「小さな飴と大きな飴」のような、
全員に配る飴の個数は決まっているが、
総重量を揃えつつ大きな飴の総数を最大化したい、 というタイプの問題を解説します。

問題文は読めるけれど、

  • X と Y が出てくると難しく感じる
  • Ai 個配ると言われると数式に落とすのが難しい
  • 「全員の総重量が同じ」という条件を式にするのが苦手

という初級〜中級者向けに、「問題文 → 数式 → 条件整理 → 実装」という流れを丁寧に追っていきます。 キーワードは次の3つです。

  • 総重量
  • 重さの差(Y−X)
  • 大きな飴の最大個数

問題の要点整理:何を満たせば良いのか?

問題設定のポイントをまとめると次の通りです。

  • 小さな飴の重さは X グラム
  • 大きな飴の重さは Y グラム(X < Y)
  • 子供 i には Ai 個の飴を配る(小+大の合計)
  • 全員の総重量を等しくしたい
  • さらに大きな飴の総数を最大化したい

ここから、小さな飴の数を s_i、大きな飴の数を l_i として数式化していきます。

数式化してみよう:X と Y と Ai の関係を式にする

子供 i について、小さな飴と大きな飴の個数を以下で表します。

  • s_i:小さい飴の数
  • l_i:大きい飴の数

合計個数の条件は s_i + l_i = Ai。 総重量は X × s_i + Y × l_i。

全員の総重量を W とすると、

W = X × s_i + Y × l_i

ここで s_i = Ai – l_i を代入すると、

W = X × Ai + (Y – X) × l_i

この式から「大きな飴を1つ増やすと総重量が (Y – X) 増える」ことがわかります。

可能条件の導出1:全員の総重量を揃えられるか?

総重量 W を比較すると、 X × A1 + D × l1 = X × Ai + D × li が必要になります(D = Y – X)。

この式から導かれる結論は次の通りです。

差分 (A1 – Ai) が D と X の最大公約数を使った特定の周期で一致していないと、 どんな配り方でも W を揃えることはできません。

判定手順は以下です。

  1. D = Y – X を求める
  2. g = gcd(D, X) を求める
  3. mod = D / g を求める
  4. (Ai – A1) が mod の倍数かチェックする

1つでも外れると解なし(-1)となります。

可能条件の導出2:総重量 W の範囲

l_i の最小値と最大値から、 各子供の総重量は次の範囲に入ります。

  • 最低重量:X × Ai(全部小さい飴)
  • 最高重量:Y × Ai(全部大きい飴)

よって W は全員の範囲を満たす必要があり、

max(X × Ai) ≤ W ≤ min(Y × Ai)

が条件となります。範囲が重ならなければ解なしです。

大きな飴の総数を最大化する考え方

l_i = (W – X × Ai) / D より、大きな飴の総数 L は W に対して単調増加します。

つまり、許される W の中で最大の W を使えば良い、ということです。

実装方針

1. 前処理

  • D、g、mod を求める
  • すべての Ai について (Ai – A1) の mod 判定を行う

2. W の範囲決定

W_low = max(X × Ai)
W_high = min(Y × Ai)

W_low > W_high の場合は即 -1。

3. W の探索

W は一定周期(D の倍数)でしか取り得ないため、 最大になるように調整しながら探します。

4. 整数性の確認

求めた W で各 l_i が整数かつ 0 〜 Ai の範囲かチェックします。

5. 大きい飴の総数を計算

すべて問題なければ L = Σ (W – X × Ai) / D を出力します。

実装例(ケビン作成、Typescript)

NumberだとWAが出るので、BigIntにしてます。

import * as fs from "fs";

const gcdBigInt = (a: bigint, b: bigint): bigint => {
	a = a < 0n ? -a : a;
	b = b < 0n ? -b : b;
	while (b !== 0n) {
		const t = a % b;
		a = b;
		b = t;
	}
	return a;
};

const modBigInt = (a: bigint, m: bigint): bigint => {
	const r = a % m;
	return r < 0n ? r + m : r;
};

function main(input: string): void {
	const trimmedInput = input.trim();
	const [rawNXY, rawAs] = trimmedInput.split(/\r?\n/);
	const [N, rawX, rawY] = rawNXY.split(" ").map(Number);
	const X = BigInt(rawX);
	const Y = BigInt(rawY);
	const As = rawAs.split(" ").map(BigInt);

	const D = Y - X;
	const g = gcdBigInt(D, X);
	const M = D / g;
	const A0 = As[0];
	
	for (let i = 1; i < As.length; i++) {
		if (modBigInt(As[i] - A0, M) !== 0n) {
			console.log(-1);
			return;
		}
	}
	
	let L = X * As[0];
	let R = Y * As[0];
	for (let i = 0; i < As.length; i++) {
		const Ai = As[i];
		const minW = X * Ai;
		const maxW = Y * Ai;
		if (minW > L) L = minW;
		if (maxW < R) R = maxW;
	}
	
	if (L > R) {
		console.log(-1);
		return;
	}

	const k = modBigInt(X*A0, D);
	const rem = modBigInt(R - k , D);
	const W = R - rem;
	
	let total = 0n;
	for (let i = 0; i < N; i++) {
		const Ai = As[i];
		const numerator = W - X * Ai; 
		const bi = numerator / D; 
		total += bi;
	}
	
	console.log(total.toString());
}

const inputData: string = fs.readFileSync("/dev/stdin", "utf8")
main(inputData);

まとめ:数式化で「重さの差」を味方にする

今回の問題のポイントは以下です。

  • 総重量は X × Ai + (Y – X) × li の形で書ける
  • 重さの差 D = Y – X が最重要
  • Ai の周期性チェックで「解が存在するか」が決まる
  • W の範囲条件と周期性を使うと実装が可能
  • 大きな飴の総数は W の最大値で決まる

数式化を通じて問題全体がシンプルになり、
「X、Y、Ai の関係をどう扱うか」が見えてきます。

皆様の参考になれば幸いです!

デイトラでWeb制作を学ぶ ※本リンクはアフィリエイトを含みます。