[BZOJ1189/Luogu3191][HNOI2007]紧急疏散EVACUATE

题目链接:

BZOJ1189

Luogu3191

\(Duliu\)网络流

首先,很容易看出二分答案+网络流的方法:

二分答案,将每一扇门拆成相应个数的节点。

对于每一个人,与源点连边,流量为\(1\),代表一个人。

然后对于每个人跑一遍最短路(边权均为\(1\),直接\(BFS\)),向\(Ta\)能够到达的门相应时间连边,容量为\(1\)

每一扇门的每个时间节点向汇点连边,容量为\(1\),表示能够通过\(1\)人。

于是一边吐槽水题,一边愉快的写了起来,然后发现\(TLE\)了。

怎么优化呢?改善建图方式。

对于每一扇门,时间\(i\)的节点向\(i+1\)连边,容量为\(+\infty\),表示不能走这一扇就走下一扇,然后对于每个人,向\(Ta\)第一时间能够到达的节点连边即可,容量为\(1\)

这么做以后,就跑得飞快了。

话说这题好难写。。

#include <queue>
#include <cstdio>
#include <cstring>

inline int Min(int a,int b){return a<b?a:b;}
inline int Max(int a,int b){return a>b?a:b;}

int n,m,Pec,Doc,St,Ed;
int Ds[25][25][25][25],IDS[25][25][205],IDC;
//Ds[i][j][x][y]为(i,j)到(x,y)的最短距离
//IDS用于离散化节点编号
int Head[130005],Next[200005],To[200005],Val[200005],En;
int Dis[130005],Cnt[130005],Cur[130005],Pre[130005];
char Map[25][25];
struct Point{int x,y;};
const int nx[]={-1,1,0,0},ny[]={0,0,-1,1};

inline int ID(int x,int y,int z)
{
    if(!IDS[x][y][z])IDS[x][y][z]=++IDC;
    return IDS[x][y][z];
}

inline void Add(int x,int y,int z)
{
    Next[++En]=Head[x],To[Head[x]=En]=y,Val[En]=z;
    Next[++En]=Head[y],To[Head[y]=En]=x,Val[En]=0;
}

void BFS(int Sx,int Sy)//计算(Sx,Sy)到其它节点的距离
{
    int (*f)[25]=Ds[Sx][Sy];
    std::queue<Point> q;
    q.push((Point){Sx,Sy});
    memset(f,0x3f,sizeof(int)*25*25);
    f[Sx][Sy]=0;
    for(;!q.empty();q.pop())
    {
        int x=q.front().x,y=q.front().y;
        for(int i=0;i<4;++i)
        {
            int wx=x+nx[i],wy=y+ny[i];
            if(wx<1||wx>n||wy<1||wy>m)continue;
            if(f[wx][wy]<=f[x][y]+1||Map[wx][wy]=='X')continue;
            f[wx][wy]=f[x][y]+1;
            if(Map[wx][wy]=='.')q.push((Point){wx,wy});//门1s只能通过一人
        }
    }
}

void Build(int Tim)//建图,时间为Tim
{
    memset(Head,0,sizeof Head),En=1;
    memset(IDS,IDC=0,sizeof IDS);
    St=n*m+Tim*Doc+1,Ed=St+1;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(Map[i][j]=='D')
                for(int k=1;k<=Tim;++k)
                {
                    Add(ID(i,j,k),Ed,1);
                    if(k<Tim)Add(ID(i,j,k),ID(i,j,k+1),0x3f3f3f3f);//向后一条节点连边,容量Inf
                }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            if(Map[i][j]=='.')
            {
                Add(St,ID(i,j,1),1);
                for(int x=1;x<=n;++x)
                    for(int y=1;y<=m;++y)
                        if(Map[x][y]=='D')
                            if(Ds[i][j][x][y]<=Tim)
                                Add(ID(i,j,1),ID(x,y,Ds[i][j][x][y]),1);//向第一个门连边。
            }
}

int Augment()
{
    int Res=0x3f3f3f3f;
    for(int x=Ed;x!=St;x=To[Pre[x]^1])Res=Min(Res,Val[Pre[x]]);
    for(int x=Ed,i;x!=St;x=To[i^1])Val[i=Pre[x]]-=Res,Val[i^1]+=Res;
    return Res;
}

int ISAP()//简单的网络流
{
    memset(Dis,0,sizeof Dis);
    memcpy(Cur,Head,sizeof Cur);
    memset(Cnt,0,sizeof Cnt);
    Cnt[0]=Ed;
    int Flow=0,x=St;
    while(Dis[St]<Ed)
    {
        if(x==Ed)Flow+=Augment(),x=St;
        bool Flag=false;
        for(int i=Cur[x],y;i;i=Next[i])
            if(Val[i]&&Dis[y=To[i]]+1==Dis[x])
                Flag=true,Cur[x]=Pre[y]=i,x=y,i=0;
        if(Flag)continue;
        int Wd=Ed-1;
        for(int i=Head[x];i;i=Next[i])
            if(Val[i])Wd=Min(Wd,Dis[To[i]]);
        if(!--Cnt[Dis[x]])break;
        ++Cnt[Dis[x]=Wd+1],Cur[x]=Head[x];
        if(x!=St)x=To[Pre[x]^1];
    }
    return Flow;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        scanf("%s",Map[i]+1);
        for(int j=1;j<=m;++j)
            if(Map[i][j]=='.')++Pec;//人的数量
            else if(Map[i][j]=='D')++Doc;//门的数量
    }
    if(!Pec)return puts("0"),0;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            BFS(i,j);
    int l=0,r=202;
    while(l<r)
    {
        int Mid=(l+r)>>1;
        Build(Mid);
        if(ISAP()==Pec)r=Mid;
        else l=Mid+1;
    }
    if(l>200)puts("impossible");
    else printf("%d\n",l);
    return 0;
}

转载于:https://www.cnblogs.com/LanrTabe/p/10186892.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值