AtCoder

ABC324 F問題(Beautiful Path)を解く

AtCoder_ABC324_F

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

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

F問題 Beautiful Path(Difficulty : 1815)

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

ABC324 F問題 Beautiful Path

求める最大値を値の二分探索で求めます。判定関数でDPを使います。AtCoder Problems による Difficulty は、1815 でした。

解答案

最大値を解の二分探索で求めます。具体的には、$P$ 上のすべての辺の美しさの総和を、$P$ 上のすべての辺のコストの総和で割った値が $X$ 以上になるか判定する関数 is_OK の結果から値を絞り込んでいきます。1回の呼び出しで値の範囲が半分になります。100回繰り返すと $2^{100} \doteqdot 10^{30}$ となり、10進数30桁の精度となります。

関数 is_OK の実装を考えます。頂点$1$から頂点$N$の辺のパスを $i_1, \dots, i_m$ とすると、判定式は以下となります。

$$\frac{\sum_{k=1}^m b_{ik}}{\sum_{k=1}^m c_{ik}} \geqq X$$

この式は以下のように書き換えます。

$$\sum_{k=1}^m b_{ik} \geqq \sum_{k=1}^m c_{ik} X, \; \sum_{k=1}^m (b_{ik} – c_{ik} X) \geqq 0$$

各辺の重みを $b_i – c_i X$ として、辺の和が 0 以上か判定します。グラフの辺は、制約から小さい頂点から大きい頂点に向かうため、以下の漸化式によって求めることができます。

dp[vi] = max(dp[vi], dp[ui] + bi – ci X)

辺 ui → vi を、ui が小さい方から処理することにより計算量 $O(M)$ により、dp[N] を求めることができます。この漸化式の考え方は、ABC309E問題解説)と同じです。

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

上記の考察を実装します。

  • 読み取った辺は、ui とインデックスを合わせて小さい順にソートしておきます(35行目)。
  • 解の二分探索を求めるコードは定番となります(37ー49行目)。
  • 判定関数 is_OK で、DP を用いて判定を行います(10ー21行目)。
  • 浮動小数点数は printf で出力しました(51行目)。

以下は、C++のプログラムです。

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

#define INF (1ULL << 60)

int n, m;
vector<pair<int, int>> u;
vector<int> v, b, c;

bool is_OK(double mid)
{
	vector<double> dp(n + 1, -1.0 * INF);
	dp[1] = 0.0;
	for (int i = 0; i < m; ++i) {
		int ut = u[i].first;
		int index = u[i].second;
		dp[v[index]] = max(dp[v[index]], dp[ut] + b[index] - mid * c[index]);
	}

	return dp[n] >= 0.0;
}

int main()
{
	cin >> n >> m;
	u.resize(m);
	v.resize(m);
	b.resize(m);
	c.resize(m);
	for (int i = 0; i < m; ++i) {
		int t;
		cin >> t >> v[i] >> b[i] >> c[i];
		u[i] = make_pair(t, i);
	}
	sort(u.begin(), u.end());

	double ng = 10001.0;;
	double ok = 0.0;

	int count = 0;
	while (count < 100) {
		++count;
		double mid = (ok + ng) / 2.0;
		if (is_OK(mid)) {
			ok = mid;
		} else {
			ng = mid;
		}
	}

	printf("%.16lf\n", ok);

	return 0;
}

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

Python プログラム例(ABC324F)

基本的な考え方は同じです。浮動小数点数は、f文字列で出力しています(43行目)。以下となります。

"""AtCoder Beginner Contest 324 F"""


INF = 1 << 60


def is_OK(mid):
    global n, m, u, v, b, c

    dp = [-1.0 * INF] * (n + 1)
    dp[1] = 0.0
    for i in range(m):
        ut = u[i][0]
        index = u[i][1]
        dp[v[index]] = max(dp[v[index]],
                           dp[ut] + b[index] - mid * c[index])

    return dp[n] >= 0.0


n, m = map(int, input().split())
u = [(0, 0)] * m
v = [0] * m
b = [0] * m
c = [0] * m
for i in range(m):
    t, v[i], b[i], c[i] = map(int, input().split())
    u[i] = (t, i)
u.sort()

ng = 10001.0
ok = 0.0

count = 0
while count < 100:
    count += 1
    mid = (ok + ng) / 2.0
    if is_OK(mid):
        ok = mid
    else:
        ng = mid

print(f"{ok:.16f}")

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

最後に

この問題は、大きな枠組みは「浮動小数点数の解の二分探索」です。判定関数には、「小さい頂点から決まってくるDP」を使って解くことができました。

公式解説を参考にしながらではありますが、わたしにとって難易度が高い問題を解くことができました。

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

COMMENT

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

CAPTCHA