AtCoder

ABC319 D問題(Minimum Width)を解く

AtCoder_ABC319_D

AtCoder が提供しているABC(AtCoder Beginner Contest)319 のD問題をC++とPythonで解いてみました。ABC319は、2023年9月9日21:00に実施されました。

AtCoder の紹介はこちらに、プログラミングの方針はこちらに記事があります。

D問題 Minimum Width(Difficulty : 631)

問題はリンク先をご覧ください。

ABC319 D問題 Minimum Width

解の二分探索を使って解くことができました。AtCoder Problems による Difficulty は 631 でした。

解答案

二分探索で求めることができます。めぐる式と呼ばれる二分探索の実装を参考にしました。ABC299D問題解説)、ABC309C問題解説)、ABC312C問題解説)でも解説しています。定番の考え方と言えます。

C++ プログラム例(ABC319D)

以下のコードパターンを実装します。

  • 関数 is_OK: この関数は条件を満たすか満たさないか判定します。
    この問題の場合、与えられた幅 mid の行に単語を詰めていって、m 行以内に収まるか判定しています。
  • 二分探索を行うコード(43ー53行目): このコードは変数 ng と ok を定める以外は、変更する必要がありません。
    • ng は 0 となります(幅 0 は明らかに解とならない)。
    • ok は、m=1 の場合に、長さ Li(最大 109)の単語を n(最大 2 × 105)個入れる必要があるため、1015 を設定しました(43、44行目)。

関数 is_OK の計算量は $(N)$、呼び出し回数は、$\log_2 10^{15}$ が上限となるため間に合います。

以下が、C++プログラムとなります。

#include <bits/stdc++.h>
using namespace std;

typedef long long int ll;

int n, m;
vector<ll> L;

bool is_OK(ll mid)
{
	int line = 0;
	ll remain = mid;
	for (int i = 0; i < n; ++i) {
		if (L[i] > mid) {
			return false;
		}
		if (L[i] <= remain) {
			remain -= L[i];
			if (remain > 0) {
				--remain;
			}
		} else {
			++line;
			remain = mid - L[i];
			if (remain > 0) {
				--remain;
			}
		}
	}
	++line;

	return line <= m;
}

int main()
{
	cin >> n >> m;
	L.resize(n);
	for (int i = 0; i < n; ++i) {
		cin >> L[i];
	}

	ll ng = 0LL;
	ll ok = 1000000000000000LL;

	while (abs(ok - ng) > 1) {
		ll mid = (ok + ng) / 2LL;
		if (is_OK(mid)) {
			ok = mid;
		} else {
			ng = mid;
		}
	}

	cout << ok << endl;

	return 0;
}

AC(Accepted=正しいプログラム)と判定されました。

Python プログラム例(ABC319D)

Python 版も基本的な考え方は同じです。以下となります。

"""AtCoder Beginner Contest 319 D"""


def is_OK(mid):
    global n
    global m
    global L

    line = 0
    remain = mid
    for i in range(n):
        if L[i] > mid:
            return False
        if L[i] <= remain:
            remain -= L[i]
            if remain > 0:
                remain -= 1
        else:
            line += 1
            remain = mid - L[i]
            if remain > 0:
                remain -= 1
    line += 1

    return line <= m


n, m = map(int, input().split())
L = list(map(int, input().split()))

ng = 0
ok = 10 ** 15
while abs(ok - ng) > 1:
    mid = (ok + ng) // 2
    if is_OK(mid):
        ok = mid
    else:
        ng = mid

print(ok)

こちらも「AC」と判定されました。

最後に

「解の二分探索」は、関数 is_OK、変数 ng と ok を適切に設定するだけで、多くの問題を解くことができます。今回の問題は、特に直前にブログ記事にした ALDS1 4_D問題と設定が似ていました(行に文字を詰める代わりに、トラックに荷物を積める問題になっていました)。このような手の内化できている問題を増やしていきたいです。

引き続き ABC の問題を紹介していきます。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA