[SMOJ2213]停车场

97 篇文章 0 订阅
10 篇文章 0 订阅

首先要发现数据范围是比较小,如果是传统的图论题,一般不会只有这么小。而再联想最近所做的二分图匹配,似乎有些吻合。当然,仅凭这个无法作出判断。

刚读题的时候,我还以为是搜索题。但是仔细分析一下,很多条件和限制都说明无法直接用搜索解决。
最明显的一点,直接搜索无法处理:每辆车可以停进若干停车位,每个车位只能停一辆车。而要求的是一种分配的方案,使所有车中最大的移动时间最小。

看到最小化最大值,自然而然就应该要想到二分。很遗憾的是,比赛的时候,我做到这里所剩的时间已经不多了,也没有用心发掘题目的本质,以致错失正解。
最终要求的是花费时间,那么不妨就二分这个值。显然,假设当前要考虑花费 k 个单位的时间,如果所有车都能在 k 个单位的时间内找到自己的归宿,且相互之间不会冲突,那么我们就认为 k 个单位的时间应当是足够的,可以尝试用更少的时间;否则时间太短了,无法完成任务,应该用更多的时间。

则现在问题的关键转化为:如何判断所有车能否在 k 个单位时间内停入一个各不相同的车位?
首先明确一点,每辆车到各个停车场的距离可以预先算出,因为各辆车的开车过程(只要最终不停在一起)是不会相互影响的,允许多台汽车同时进入某个可通行格子。所以,以每辆车为起点各跑 bfs 即可,数据范围也比较小,这一步不会占用太多时间。
现在回到我们的问题。可以认为,“停车”就是将车与停车场进行匹配的过程,车到一个停车场的距离为 d ,等价于它们之间有一条费用为 d 的边,于是可以据此建立二分图。因为要在 k 个时间内停完车,因此费用大于 k 的边就要被放弃,只有小于等于 k 的边才是有效的。
但是,在网络中构图的时候,只需把这些有效边连接的点之间连一条容量为 1 的边即可。换言之,只要在限定时间内能够到达,具体的值,我们并不关心。

这样,就可以把能够在 k 个时间内匹配的车和停车场建立二分图,再加上源和汇,跑一遍最大流,得到二分图的最大匹配。如果最大匹配恰好等于车的数量,就说明所有车可以在 k <script type="math/tex" id="MathJax-Element-11">k</script> 个单位时间内成功停车。
需要注意的是对特殊情况的判断。当没有车的时候,相当于所有移动已经完成,则应该输出 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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值