AtCoder が提供しているABC(AtCoder Beginner Contest)301 のE問題をC++で解いてみました。ABC301は、2023年5月13日21:00に実施されました。
AtCoder の紹介はこちらに、プログラミングの方針はこちらに記事があります。
E問題 Pac-Takahashi(Difficulty : 1673)
問題はリンク先をご覧ください。
巡回セールスマン問題と同じように bitDP を使います。AtCoder Problems による Difficulty は 1673 でした。
解答案
スタート地点、ゴール地点、お菓子があるマスを頂点と考えて巡回セールスマン問題を解きます。ただし、巡回するのではなく、スタート地点からゴール地点に行く経路を考えます。
C++ プログラム例(ABC301E)
実装は、かなり長くなりました。
- スタート地点、お菓子があるマス、ゴール地点を配列 i2p に格納します(64-73行目)。
- すべての頂点間の距離を BFS で求めます(75ー84行目)。BFS を何回も使うため、別関数 bfs で処理します(11ー38行目)。
- 巡回セールス問題として解きます。
- スタート地点からゴール地点まで T 回以下の移動で到達できる場合、dp[*][n-1] の * のビット数-2が訪れるお菓子のマスとなります(108ー120行目)。
以下が、C++プログラムとなります。
#include <bits/stdc++.h>
using namespace std;
#define INF (1 << 30)
int h, w;
vector<string> a;
int di[4] = {1, 0, -1, 0};
int dj[4] = {0, 1, 0, -1};
vector<vector<int>> bfs(int i, int j)
{
vector<vector<int>> dist(h, vector<int>(w, -1));
queue<pair<int, int>> que;
que.push(make_pair(i, j));
dist[i][j] = 0;
while (!que.empty()) {
auto now = que.front();
que.pop();
int now_i = now.first;
int now_j = now.second;
for (int d = 0; d < 4; ++d) {
int next_i = now_i + di[d];
int next_j = now_j + dj[d];
if ((0 <= next_i)&&(next_i < h)
&&(0 <= next_j)&&(next_j < w)
&&(dist[next_i][next_j] == -1)
&&(a[next_i][next_j] != '#')) {
que.push(make_pair(next_i, next_j));
dist[next_i][next_j] = dist[now_i][now_j] + 1;
}
}
}
return dist;
}
int main()
{
int t;
cin >> h >> w >> t;
a.resize(h);
for (int i = 0; i < h; ++i) {
cin >> a[i];
}
int si, sj;
int gi, gj;
for (int i = 0; i < h; ++i) {
for (int j = 0; j < w; ++j) {
if (a[i][j] == 'S') {
si = i;
sj = j;
}
if (a[i][j] == 'G') {
gi = i;
gj = j;
}
}
}
vector<pair<int, int>> i2p;
i2p.push_back(make_pair(si, sj));
for (int i = 0; i < h; ++i) {
for (int j = 0; j < w; ++j) {
if (a[i][j] == 'o') {
i2p.push_back(make_pair(i, j));
}
}
}
i2p.push_back(make_pair(gi, gj));
int n = i2p.size();
vector<vector<int>> dist(n, vector<int>(n, INF));
for (int i = 0; i < n; ++i) {
vector<vector<int>> d = bfs(i2p[i].first, i2p[i].second);
for (int j = 0; j < n; ++j) {
if (d[i2p[j].first][i2p[j].second] != -1) {
dist[i][j] = d[i2p[j].first][i2p[j].second];
}
}
}
vector<vector<int>> dp(1 << n, vector<int>(n, INF));
dp[1][0] = 0;
for (int i = 1; i < (1 << n); ++i) {
for (int j = 0; j < n; ++j) {
if ((i & (1 << j)) == 0) {
continue;
}
if (dp[i][j] == INF) {
continue;
}
for (int k = 1; k < n; ++k) {
if (dist[j][k] == INF) {
continue;
}
if ((i & (1 << k)) == 0) {
int v = (i | (1 << k));
dp[v][k] = min(dp[v][k], dp[i][j] + dist[j][k]);
}
}
}
}
int result;
if ((dist[0][n - 1] <= t)) {
result = 0;
} else {
cout << -1 << endl;
return 0;
}
for (int i = 1; i < (1 << n); ++i) {
if (((i & 1) != 0)&&(dp[i][n - 1] <= t)) {
result = max(result, __builtin_popcount(i) - 2);
}
}
cout << result << endl;
return 0;
}
AC(Accepted=正しいプログラム)と判定されました。
今回は、Python版の紹介は省略します。
最後に
巡回セールスマン問題に近い問題です。巡回はしませんが、同じ考え(bitDP)で解くことができました。理解度を高めるために記事にしました。
引き続き ABC の問題を紹介していきます。