题目链接:
(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
",l);
return 0;
}