首先要发现数据范围是比较小,如果是传统的图论题,一般不会只有这么小。而再联想最近所做的二分图匹配,似乎有些吻合。当然,仅凭这个无法作出判断。
刚读题的时候,我还以为是搜索题。但是仔细分析一下,很多条件和限制都说明无法直接用搜索解决。
最明显的一点,直接搜索无法处理:每辆车可以停进若干停车位,每个车位只能停一辆车。而要求的是一种分配的方案,使所有车中最大的移动时间最小。
看到最小化最大值,自然而然就应该要想到二分。很遗憾的是,比赛的时候,我做到这里所剩的时间已经不多了,也没有用心发掘题目的本质,以致错失正解。
最终要求的是花费时间,那么不妨就二分这个值。显然,假设当前要考虑花费
k
个单位的时间,如果所有车都能在
则现在问题的关键转化为:如何判断所有车能否在
首先明确一点,每辆车到各个停车场的距离可以预先算出,因为各辆车的开车过程(只要最终不停在一起)是不会相互影响的,允许多台汽车同时进入某个可通行格子。所以,以每辆车为起点各跑 bfs 即可,数据范围也比较小,这一步不会占用太多时间。
现在回到我们的问题。可以认为,“停车”就是将车与停车场进行匹配的过程,车到一个停车场的距离为
d
,等价于它们之间有一条费用为
但是,在网络中构图的时候,只需把这些有效边连接的点之间连一条容量为 1 的边即可。换言之,只要在限定时间内能够到达,具体的值,我们并不关心。
这样,就可以把能够在
需要注意的是对特殊情况的判断。当没有车的时候,相当于所有移动已经完成,则应该输出 0。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
typedef pair <int, int> pii;
const int MAXN = 200 + 10;
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, -1, 1};
const int INF = 0x3f3f3f3f;
struct Edge {
Edge *next;
int cap;
int dest;
} edges[MAXN * MAXN], *current, *first_edge[MAXN];
int n, m, s, t;
char matrix[MAXN][MAXN]; int rev[MAXN][MAXN]; //若 (i, j) 为停车场,则 rev[i][j] 为该停车场在 parks 数组中的下标
int cnt_cars, cnt_parks, dis[MAXN][MAXN]; //dis[i][j] 为第 j 辆车到第 i 个停车场的距离
pii cars[MAXN], parks[MAXN];
bool vis[MAXN][MAXN], vis2[MAXN]; //分别为 bfs 和 dfs 服务
void bfs(int car_id) { //预处理第 car_id 辆车到停车场的距离
queue < pair <pii, int> > q; memset(vis, false, sizeof vis); vis[cars[car_id].first][cars[car_id].second] = true;
for (q.push(make_pair(make_pair(cars[car_id].first, cars[car_id].second), 0)); !q.empty(); q.pop()) {
pii pos = q.front().first; int step = q.front().second;
for (int i = 0; i < 4; i++) {
int nx = pos.first + dx[i], ny = pos.second + dy[i];
if (nx >= 0 && nx < n && ny >= 0 && ny < m && matrix[nx][ny] != 'X' && !vis[nx][ny]) {
if (matrix[nx][ny] == 'P') dis[rev[nx][ny]][car_id] = step + 1;
vis[nx][ny] = true;
q.push(make_pair(make_pair(nx, ny), step + 1));
}
}
}
}
Edge *counterpart(Edge *x) {
return edges + ((x - edges) ^ 1);
}
void insert(int u, int v, int c) {
current -> next = first_edge[u];
current -> cap = c;
current -> dest = v;
first_edge[u] = current ++;
}
int dfs(int u, int f) {
if (u == t) return f;
if (vis2[u]) return 0; else vis2[u] = true;
for (Edge *p = first_edge[u]; p; p = p -> next)
if (p -> cap)
if (int res = dfs(p -> dest, min(f, p -> cap))) {
p -> cap -= res;
counterpart(p) -> cap += res;
return res;
}
return 0;
}
bool isok() {
int ans = 0;
while (true) {
memset(vis2, false, sizeof vis2);
if (int res = dfs(s, INF)) ans += res; else break;
}
return ans == cnt_cars; //满流则当前时间足够
}
int make_graph(int limit) { //根据时间上限建图,并返回所选边中容量最大的(只为二分取初值服务)
current = edges; fill(first_edge, first_edge + t + 1, (Edge*)0); int res = INF;
for (int i = 0; i < cnt_cars; i++) {
insert(s, i + 1, 1); insert(i + 1, s, 0);
for (int j = 0; j < cnt_parks; j++)
if (dis[j][i] <= limit) {
insert(i + 1, j + cnt_cars + 1, 1), insert(j + cnt_cars + 1, i + 1, 0);
res = max(res, dis[j][i]);
}
}
for (int i = 0; i < cnt_parks; i++) insert(i + cnt_cars + 1, t, 1), insert(t, i + cnt_cars + 1, 0);
return res;
}
int calc() {
if (!cnt_cars) return 0;
s = 0; t = cnt_cars + cnt_parks + 1;
int l = 0, r = make_graph(INF - 1); //左开右闭
if (!isok()) return -1;
while (l + 1 < r) {
int mid = l + r >> 1; make_graph(mid);
if (isok()) r = mid; else l = mid;
}
return r;
}
int main(void) {
freopen("2213.in", "r", stdin);
freopen("2213.out", "w", stdout);
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++) {
scanf("%s", matrix[i]);
for (int j = 0; j < m; j++)
if (matrix[i][j] == 'C') cars[cnt_cars++] = make_pair(i, j);
else if (matrix[i][j] == 'P') { rev[i][j] = cnt_parks; parks[cnt_parks++] = make_pair(i, j); }
}
memset(dis, 0x3f, sizeof dis);
for (int i = 0; i < cnt_cars; i++) bfs(i);
// for (int i = 0; i < cnt_parks; i++) {
// for (int j = 0; j < cnt_cars; j++) printf("%d ", dis[i][j]);
// putchar('\n');
// }
printf("%d\n", calc());
return 0;
}