【GDOI2017模拟8.12】躲藏

Description

给出一个n*m的网格图,图中有一些障碍节点。
现在有A个男生和B个女生,还有一个小标。
男生要和女生配对,小标可以和任何一个人配对。
每一对CP(雾)只能待在一个点。一个点只能有一对CP。
现在给出A+B+1个人的初始坐标,和他们的移动速度(即移动到4相邻格子所需的时间)
所有人同时移动,求完成配对的最小时间。

Solution

首先,小标的性别完全可以确定,不用管。
然后,我们发现我们要让最长的移动时间最小。
二分答案,判定显然用网络流。
如何建模?
首先,因为每个男生和每个女生都要在一个点,我们不妨把这个点放在中间,左边男生连它,右边女生连他。这里如果有边相连就表示这个人能够在二分上界中到达这个点。
每个点拆点,自己连1.表示这里只能有一对CP。
然后跑最大流就是最多能匹配的CP数,判断一下就好了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define L 505
#define N 2005
#define M 810005
using namespace std;
typedef long long ll;
const ll inf=1e13;
struct note{int x,y;ll t;}a[L],b[L],q[N];
int n,m,A,B,S,T,l,x,y,dis[N],d[N];
ll dist[L*2][L],time;
int last[N],next[M],t[M],f[M],g[4][2]={0,1,1,0,0,-1,-1,0};
char s[L][L];
void add(int x,int y,int z) {
    t[++l]=y;f[l]=z;next[l]=last[x];last[x]=l;
    t[++l]=x;f[l]=0;next[l]=last[y];last[y]=l;
}
int get(int x,int y) {return (x-1)*m+y;}
bool bfs() {
    memset(dis,0,sizeof(dis));dis[S]=1;
    int i=0,j=1;d[1]=S;
    while (i<j) rep(k,d[++i]) 
        if (!dis[t[k]]&&f[k]) dis[t[k]]=dis[d[i]]+1,d[++j]=t[k];
    return dis[T];
}
int dinic(int x,int y) {
    if (x==T) return y;
    int now=0;
    rep(i,x) if (dis[t[i]]==dis[x]+1&&f[i]) {
        int k=dinic(t[i],min(y,f[i]));
        f[i]-=k;f[i^1]+=k;y-=k;now+=k;
        if (!y) break;
    }
    if (!now) dis[x]=-1;
    return now;
}
bool check(ll x) {
    memset(last,0,sizeof(last));l=1;int ans=0;
    fo(i,1,A) add(S,i,1);fo(i,1,B) add(i+A+2*n*m,T,1);
    fo(i,1,A) fo(j,1,n*m) if (dist[i][j]<=x) add(i,j+A,1);
    fo(i,1,B) fo(j,1,n*m) if (dist[i+A][j]<=x) add(j+A+n*m,i+A+2*n*m,1);
    fo(i,1,n*m) add(i+A,i+A+n*m,1);
    while (bfs()) ans+=dinic(S,0x7fffffff);
    if (ans==A) return 1;else return 0;
}
void len(int v,int x,int y,ll t) {
    dist[v][get(x,y)]=0;
    int i=0,j=1;q[1].x=x;q[1].y=y;
    while (i<j) {
        x=q[++i].x,y=q[i].y;
        fo(k,0,3) {
            int xx=x+g[k][0],yy=y+g[k][1],zz=get(xx,yy);
            if (xx<1||xx>n||yy<1||yy>m||s[xx][yy]=='#'||dist[v][zz]!=dist[0][0]) continue;
            q[++j].x=xx;q[j].y=yy;q[j].t=q[i].t+t;dist[v][zz]=q[j].t;
        }
    }
}
int main() {
    scanf("%d%d%d%d",&n,&m,&A,&B);S=0;T=A+B+2*n*m+2;
    if (A==B||abs(A-B)>1) {printf("-1");return 0;}
    fo(i,1,n) scanf("%s",s[i]+1);
    scanf("%d%d%lld",&x,&y,&time);memset(dist,127,sizeof(dist));
    fo(i,1,A) scanf("%d%d%lld",&a[i].x,&a[i].y,&a[i].t);
    fo(i,1,B) scanf("%d%d%lld",&b[i].x,&b[i].y,&b[i].t);
    if (A<B) a[++A].x=x,a[A].y=y,a[A].t=time;
    else b[++B].x=x,b[B].y=y,b[B].t=time;
    fo(i,1,A) len(i,a[i].x,a[i].y,a[i].t);
    fo(i,1,B) len(i+A,b[i].x,b[i].y,b[i].t);
    ll le=1,ri=inf;
    while (le<ri) {
        ll mid=(le+ri)/2;
        if (check(mid)) ri=mid;else le=mid+1;
    }
    if (le==inf) printf("-1");else printf("%lld",le);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值