POJ3057_Evacuation_二分图匹配::逃生问题

题意

给出一张地图, “D” 表示门,”.” 表示人, “X” 表示墙。人不会出现在最外面一圈,门只会出现在最外面一圈,并且一扇门一秒钟只能有一人通过。现在起火了,求所有通过门逃出地图的最短时间。如果有人逃不出去就输出 impossible。

思路

看到这个题首先想到的是最小费用流。但是,“一秒钟只能通过一个人”这个限制条件无法克服。这个问题本质上是人和门的匹配问题,因为门多了一个时间属性,所以可以用拆点法归结成一个二分图匹配的问题。

1.把每个人看成一个点,构成集合 P;把每一时刻的门看成一个点,构成集合 D。因为最大的可能时间是地图的网格总数(设想为只有一扇门,其他格都站满了人的情况),所以集合 D 的规模在可接受的范围内。
2.从每一扇门出发进行bfs,求出每个人到这扇门的最短距离。
3.对于每一扇门,利用 2 中求出来的最短时间,把能到达它的人与对应时刻的它之间连一条边。
4.依次对 D 集合中的每一个点进行匹配,一旦匹配数达到 P, 则这个点对应的时间就是需要花费的最小时间。
5.如果时间达到了上界,但匹配数依然小于 P,输出 impossible 。

需要注意的一点是,这种做法需要特判 P = 0 的情况,否则在这种情况下会输出 impossible 。

题目链接

http://poj.org/problem?id=3057

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>

using namespace std;

const int maxn = 13;

const int mx[4] = {0, 0, -1, 1};
const int my[4] = {1, -1, 0, 0};

int cas, row, col;
char Map[maxn][maxn];
int dis[maxn][maxn][maxn][maxn];//门到人的距离:这么存很暴力但是比较方便
vector<int> G[maxn * maxn * maxn * 5];
int match[maxn * maxn * maxn * 5];
bool usd[maxn * maxn * maxn * 5];
vector<int> dx, dy;//存门的坐标
vector<int> px, py;//存人的坐标

//bfs 求出门到各个人的最短距离
void bfs(int x, int y, int dis[maxn][maxn])
{
    dis[x][y] = 0;
    queue<int> qx, qy;
    qx.push(x);qy.push(y);

    while(qx.size())
    {
        x = qx.front();qx.pop();
        y = qy.front();qy.pop();
        for(int i= 0; i< 4; i++)
        {
            int cx = x + mx[i], cy = y + my[i];
            if(cx >= 0 && cx < row && cy >= 0 && cy < col
                && Map[cx][cy] == '.' && dis[cx][cy] < 0)
                {
                    dis[cx][cy] = dis[x][y] + 1;
                    qx.push(cx);
                    qy.push(cy);
                }
        }
    }
}

void add(int a, int b)
{
    G[a].push_back(b);
    G[b].push_back(a);
}

bool dfs(int v)
{
    usd[v] = true;

    for(int i= 0; i< G[v].size(); i++)
    {
        int u = G[v][i], w = match[u];
        if(w < 0 || !usd[w] && dfs(w))
        {
            match[u] = v;
            match[v] = u;
            return true;
        }
    }

    return false;
}

int main()
{
    cin >> cas;
    while(cas --)
    {
        memset(match, -1, sizeof match);
        memset(dis, -1, sizeof dis);
        dx.clear(); dy.clear();
        px.clear(); py.clear();
        for(int i= 0; i< maxn * maxn * maxn * 5; i++)
            G[i].clear();

        cin >> row >> col;

        int m = row * col;

        for(int i= 0; i< row; i++)
            for(int j= 0; j< col; j++)
            cin >> Map[i][j];

        for(int i= 0; i< row; i++)
            for(int j= 0; j< col; j++)
        {
            if(Map[i][j] == 'D')
            {
                dx.push_back(i);
                dy.push_back(j);
                bfs(i, j, dis[i][j]);
            }
            else if(Map[i][j] == '.')
            {
                px.push_back(i);
                py.push_back(j);
            }
        }

        int D = dx.size(), P = px.size();

        //重要特判
        if(P == 0)
        {
            cout << "0\n";
            continue;
        }

        for(int i= 0; i< D; i++)
            for(int j= 0; j< P; j++)
        {
            //人 和 时刻门 之间连边
            if(dis[dx[i]][dy[i]][px[j]][py[j]] > 0)
                for(int t= dis[dx[i]][dy[i]][px[j]][py[j]]; t<= m; t++)
                    add((t - 1) * D + i, m * D + j);
        }

        int sum = 0;
        //枚举匹配 时刻门
        for(int i= 0; i< m * D; i++)
        {
            memset(usd, false, sizeof usd);
            if(dfs(i))
            {
                sum ++;
                if(sum == P)
                {
                    cout << i / D + 1 << endl;
                    break;
                }
            }
        }

        if(sum < P)
            cout << "impossible\n";
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值