Evacuation POJ - 3057

题目大意,在一房间里,有多个’.'每个代表一个人,房间的的边界由’X’和’D’组成,分别代表墙和门,而且房间内部保证没有门。现在问题问所有人逃离房间(到达’D’处即表示逃离成功),而且每一扇门一个时间只能允许一个人通过,每个人只能向上下左右移动,每次移动花费一秒。逃离过程中,一个点可以容纳多个人,即每个人到每扇门的距离,不受其他人的影响。
一开始打算用网络流写,但写到后面,发现整个过程是随时间变化的,而且网络流每次只能找一条增广路,而题目中一个时间内有多个出口。这样就不好处理。网上有一种用动态流模型的解题方法。
最后改用二分图匹配去做。
用二分图匹配,还是要注意时间问题。每一个时间点可以对应多扇门,因此可以把每一扇门在一个相应的时间段,对应其中的每一个时间点,拆一个点。这样二分图的左边,就是由各扇门在各自允许的时间范围内拆成的多个门,右边就是房间里面的每个人。
接着是边的建立,先用bfs求出每一扇门到每一个人的距离,即花费的最少时间。那么每个人到每一扇门的时间可能是最少时间到最大时间内的某一个时间点。而最大时间可以去整个图的大小,这样比较方便而且能保证题目要求。这样就解决了时间的问题。
代码:

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> P;
const int inf=0x3f3f3f3f;
const int N=4e4;
char room[14][14];//存图
bool book[14][14];//bfs时标记
bool vis[N];//二分匹配时标记
int y,x,V,cnt,num;//y行x列
int xx[4]={0,1,0,-1};//bfs走的路径方向
int yy[4]={1,0,-1,0};
struct node
{
    int x,y,s;
    node(){}
    node(int a,int b,int c)
    {
        x=a;
        y=b;
        s=c;
    }
    friend bool operator < (node aa,node bb)
    {
        return aa.s>bb.s;
    }
};
P door[N];//存门的坐标
priority_queue<node>que;
vector<node>dis[N];//存每一扇门到每个人的距离
vector<int>pic[N];//二分存图
int match[N];
void bfs(int dx,int dy)
{//printf("dx=%d,dy=%d\n",dx,dy);
    while(!que.empty())
        que.pop();
    int dd=(dx-1)*x+dy;
    node w;
    w.x=dx;
    w.y=dy;
    w.s=0;
    que.push(w);
    memset(book,false,sizeof(book));
    while(!que.empty())
    {
        node now=que.top();
        que.pop();
        for(int i=0;i<4;i++)
        {
            int u=now.x+xx[i];
            int v=now.y+yy[i];
            if(u>=1&&u<=y&&v>=1&&v<=x&&room[u][v]=='.'&&!book[u][v])
            {//printf("u=%d,v=%d\n",u,v);
                node tt;
                tt.x=u;
                tt.y=v;
                tt.s=now.s+1;
                que.push(tt);
                book[u][v]=true;
                dis[dd].push_back(tt);
            }
        }
    }
}
bool dfs(int u)
{
    for(int i=0;i<pic[u].size();i++)
    {
        int v=pic[u][i];
        if(!vis[v])
        {
            vis[v]=true;
            int w=match[v];
            if(w<0||dfs(w))
            {
                match[v]=u;
                //match[u]=v;
                return true;
            }
        }
    }
    return false;
}
void solve()
{
    int ans=0;
    memset(match,-1,sizeof(match));
    for(int i=1;i<=V*cnt;i++)
    {
        memset(vis,false,sizeof(vis));
        if(dfs(i))
        {
            if(++ans==num)//匹配的次数就是已逃出的人数
            {//一个时间点对应cnt扇门
                printf("%d\n",(i-1)/cnt+1);
                return;
            }
        }
    }
    printf("impossible\n");
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&y,&x);
        V=x*y;
        memset(room,0,sizeof(room));
        memset(door,0,sizeof(door));
        memset(dis,0,sizeof(dis));
        cnt=0;//门的数量
        num=0;
        for(int i=1;i<=y;i++)
        {
            getchar();
            for(int j=1;j<=x;j++)
            {
                scanf("%c",&room[i][j]);
                if(room[i][j]=='D')
                {
                    door[++cnt].first=i;
                    door[cnt].second=j;
                }
                if(room[i][j]=='.')
                    num++;//人的数量
            }
        }
        for(int i=1;i<=cnt;i++)//bfs找门到人的距离
            bfs(door[i].first,door[i].second);
        for(int i=0;i<=cnt*V+V;i++)
            pic[i].clear();
        for(int i=1;i<=cnt;i++)//门的数量
        {
            int tt=(door[i].first-1)*x+door[i].second;
            for(int j=0;j<dis[tt].size();j++)//该扇门能够到达的人
            {
                int td=dis[tt][j].s;
                for(int k=td;k<=V;k++)//最多V个时刻,一个时刻对应cnt扇门
                    pic[(k-1)*cnt+i].push_back(V*cnt+(dis[tt][j].x-1)*x+dis[tt][j].y);//把门放在不同的时刻建图
            }
        }
        solve();
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值