B - Evacuation(二分图最大匹配,网络流,元组建图)

B - Evacuation

POJ - 3057

题意:

​ 墙壁“X”,空区域(都是人)“.”, 门“D”。

人向门移动通过时视为逃脱,门每秒能出去一个人,人可以上下左右移动,墙阻止移动。

求最优移动方案下,最后一个人逃脱的最短时间。如果有人无法安全逃脱(比如被墙围困住),则输出“impossible”。

思路:

大致思路:

​ 可以巧妙的建图来转化这个问题,我们来建立一个二分图,左边节点是人,右边节点为 (时间,门)的元组。如果该人可以在该时间到这个门,那么就有一条人 到元组的边,然后求可以使得所以人都匹配情况下的最小时间 t t t

具体实现思路:

b f s ​ bfs​ bfs出每个人与所有门的最短距离 m i n D i s [ p e o ] [ d o o r ] ​ minDis[peo][door]​ minDis[peo][door] (代码中使用 m i n d i s [ p x ] [ p y ] [ d x ] [ d y ] ​ mindis[px][py][dx][dy]​ mindis[px][py][dx][dy] 来表示)。

建立二分图:左边节点为人,右边节点为 (时间,门)的元组 ;对于每一个时间 t t t,如果有 m i n D i s [ p e o ] [ d o o r ] > = t minDis[peo][door]>=t minDis[peo][door]>=t的 都有 p e o p l e people people ( d o o r , t ) (door,t) (door,t)权值为 1 1 1的边;

然后每次增加时间 t t t,并且添加新的关于 ( d o o r , t ) (door,t) (door,t)的元组,并连接上可以连接到的人,然后求新增加的匹配数目,直到匹配数目大于 p e o p l e people people的个数即可;又因为时间 t t t最大为 n n n,所以超过 n n n就是无解了。

注:

二分时间 T T T 会超时,所以这里采用的是在原来的时间 t t t的基础上,每次将 t t t 1 1 1,直到所有人都被匹配即可。

代码:

#include<queue>
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
#define mset(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int maxn=3e4+10;
const int inf=0x3f3f3f3f;

struct edge
{
    int to,rev,cap;
    edge() {}
    edge(int to,int cap,int rev):to(to),rev(rev),cap(cap) {}
};
class EK
{
public:
    vector<edge> adja[maxn];
    int prevv[maxn],preve[maxn],top;
    void init(int n)
    {
        for(int i=0; i<n; ++i) adja[i].clear();
        top=n;
    }
    void addEdge(int u,int v,int cap)
    {
        adja[u].push_back(edge(v,cap,adja[v].size()));
        adja[v].push_back(edge(u,0,adja[u].size()-1));
    }
    void bfs(int s,int t)
    {
        queue<int> mmp;
        mset(prevv,-1);
        prevv[s]=s;
        mmp.push(s);
        while(!mmp.empty())
        {
            int u=mmp.front();
            mmp.pop();
            for(int i=0; i<adja[u].size(); ++i)
            {
                edge &e=adja[u][i];
                if(e.cap>0&&prevv[e.to]==-1)
                {
                    prevv[e.to]=u;
                    preve[e.to]=i;
                    mmp.push(e.to);
                }
            }
        }
    }
    int maxFlow(int s,int t)//多次使用的网络流
    {
        int flow=0;
        for(;;)
        {
            bfs(s,t);
            if(prevv[t]==-1)
                return flow;
            int minn=inf;
            for(int v=t; v!=prevv[v]; v=prevv[v])
            {
                minn=min(minn,adja[prevv[v]][preve[v]].cap);
            }
            flow+=minn;
            for(int v=t; v!=prevv[v]; v=prevv[v])
            {
                edge &e=adja[prevv[v]][preve[v]];
                e.cap-=minn;
                adja[v][e.rev].cap+=minn;
            }
        }

    }
};
EK kit;
int X,Y;
vector<P> peo;
vector<P> door;
int minDis[18][18][18][18];
char files[18][18];
/*
bfs出每个人与所有门的最短距离 mindis[px][py][dx][dy]
 建立二分图:    左边人,右边(时间,门)的元组  ,且minDis[peo][door]>=t的       peo到(door,t)都有权值为1的边
每次增加时间t,并且添加新的关于(door,t)的元组,并连接上可以连接到的人,然后求新增加的匹配数目,直到匹配数目大于people的个数即可。
又因为时间t最大为n,所以超过n就是无解了
*/
int dir[4][2]= {-1,0,1,0,0,-1,0,1};
struct node
{
    int x,y,s;
    node() {}
    node(int x,int y,int s):x(x),y(y),s(s) {}
};
void bfs(int x,int y)//x y坐标与其他门的距离{
{
    queue<node>mmp;
    mmp.push(node(x,y,0));
    while(!mmp.empty())
    {
        node p=mmp.front();
        mmp.pop();
        if(files[p.x][p.y]=='D')
        {
            minDis[x][y][p.x][p.y]=p.s;
            continue;
        }
        for(int i=0; i<4; ++i)
        {
            int xx=p.x+dir[i][0];
            int yy=p.y+dir[i][1];
            if(xx>=0&&xx<X&&yy>=0&&yy<Y&&files[xx][yy]!='X'&&minDis[x][y][xx][yy]==-1)
            {
                //门或者空地,不能从门道门
                minDis[x][y][xx][yy]=0;
                mmp.push(node(xx,yy,p.s+1));
            }
        }
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        mset(minDis,-1);
        peo.clear();
        door.clear();
        scanf("%d%d",&X,&Y);
        for(int i=0; i<X; ++i)
            scanf("%s",files[i]);
        for(int i=0; i<X; ++i)
        {
            for(int j=0; j<Y; ++j)
            {
                if(files[i][j]=='.')
                {
                    peo.push_back({i,j});
                    bfs(i,j);
                }
                else if(files[i][j]=='D')
                {
                    door.push_back({i,j});
                }
            }
        }
        if(peo.size()==0)
        {
            printf("0\n");
            continue;
        }
        int flow=0,ans=-1,n=X*Y;
        int dtot=door.size(),ptot=peo.size();
        kit.init(dtot*n+ptot+2);
        int endd=dtot*n+ptot+1,source=0;
        for(int i=1; i<=ptot; ++i) kit.addEdge(source,dtot*n+i,1); //源点到peo的距离
        for(int d=1; d<=dtot; ++d) //(t,door) 到endd的距离
        {
            for(int k=1; k<=n; ++k)
                kit.addEdge(dtot*(k-1)+d,endd,1);
        }
        /*
        随后每次增加时间 并且增加时间对应的一些边
        */
        for(int t=1; t<=X*Y; ++t)
        {
            for(int p=0; p<ptot; ++p)
            {
                for(int d=0; d<dtot; ++d)
                {
                    if(minDis[peo[p].first][peo[p].second][door[d].first][door[d].second]==-1)
                        continue;
                    if(minDis[peo[p].first][peo[p].second][door[d].first][door[d].second]<=t)
                        kit.addEdge(dtot*n+p+1,dtot*(t-1)+d+1,1);
                }
            }
            flow+=kit.maxFlow(source,endd);
            if(flow>=ptot){
                ans=t;
                break;
            }
        }
        if(ans==-1)
        {
            printf("impossible\n");
        }
        else
            printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值