1. 程式人生 > 實用技巧 >[FJOI2020]世紀大逃亡 題解

[FJOI2020]世紀大逃亡 題解

FJOI2020 D1T1


題目大意

給出一個由 $n$ 行 $m$ 列的點構成的網格,其中第 $1$ 行,第 $n$ 行,第 $1$ 列與第 $m$ 列為邊界,給出 $s$ 個點,求這 $s$ 個點到邊界的最小的路徑長度之和,要求路徑不能交叉。 $n*m\leq20000$。

思路分析

可以看出這是個費用流模板題。根據實測,本題資料卡 EK 單路增廣,需要多路增廣才能過。雖然多路增廣理論複雜度相同,但是本題中每次增廣只能增加 $1$ 的流量,多路增廣的確可以提高一定的效率。

#include<iostream>
#include<cstdio>
#include<queue>
#define ano ((i-1)^1)+1
using namespace std;
const int N=1e6+100,INF=0x7f7f7f7f;
int n,m,S,tot,s,t,f,ans;
int head[N],ver[2*N],Next[2*N],edge[2*N],cost[2*N];
int minf[N],pre[N],d[N];
bool v[N];
void add(int x,int y,int z,int c)
{
    ver[++tot]=y,edge[tot]=z,cost[tot]=c,Next[tot]=head[x],head[x]=tot;
    ver[++tot]=x,edge[tot]=0,cost[tot]=-c,Next[tot]=head[y],head[y]=tot;
}
bool spfa()
{
    for(int i=0;i<=t;i++)
        d[i]=INF,v[i]=0;
    queue<int> q;
    q.push(s);
    v[s]=1,d[s]=0,minf[s]=INF;
    while(q.size())
    {
        int x=q.front();q.pop();v[x]=0;
        for(int i=head[x];i;i=Next[i])
        {
            if(!edge[i])
                continue;
            int y=ver[i];
            if(d[x]+cost[i]<d[y])
            {
                d[y]=d[x]+cost[i];
                minf[y]=min(minf[x],edge[i]);
                pre[y]=i;
                if(!v[y])
                {
                    v[y]=1;
                    q.push(y);
                }
            }
        }
    }
    return d[t]!=INF;
}/*
void update()
{
    int x=t;
    while(x!=s)
    {
        int i=pre[x];
        edge[i]-=minf[t];
        edge[ano]+=minf[t];
        x=ver[ano];
    }
    f+=minf[t];
    ans+=d[t]*minf[t];
}*///單路增廣
int dinic(int x,int flow)
{
    if(x==t)
        return flow;
    v[x]=1;
    int rest=flow,use;
    for(int i=head[x];i && rest;i=Next[i])
    {
        int y=ver[i];
        if(v[y] || !edge[i] || d[y]!=d[x]+cost[i])
            continue;
        use=dinic(y,min(rest,edge[i]));
        if(!use)
            d[y]=0;
        edge[i]-=use,edge[ano]+=use,rest-=use;
        ans+=cost[i]*use;
    }
    return flow-rest;
}
int id(int x,int y,int z)
{
    return (x-1)*m+y+z*n*m;
}
void clear()
{
    t=2*n*m+1,ans=tot=f=0;
    for(int i=0;i<=t;i++)
        head[i]=pre[i]=minf[i]=0;
}
int main()
{
    //freopen("covid.in", "r", stdin);
    //freopen("covid.out", "w", stdout);
    while(scanf("%d%d%d",&n,&m,&S)!=EOF)
    {
        clear();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                add(id(i,j,0),id(i,j,1),1,0);
                if(i!=1)
                    add(id(i,j,1),id(i-1,j,0),1,1);
                if(i!=n)
                    add(id(i,j,1),id(i+1,j,0),1,1);
                if(j!=1)
                    add(id(i,j,1),id(i,j-1,0),1,1);
                if(j!=m)
                    add(id(i,j,1),id(i,j+1,0),1,1);
                if(i==1 || i==n || j==1 || j==m)
                    add(id(i,j,1),t,1,0);
            }
        for(int i=1,x,y;i<=S;i++)
        {
            scanf("%d%d",&x,&y);
            add(s,id(x,y,0),1,0);
        }/*
        while(spfa())
            update();*///單路增廣
        while(spfa())
        {
            int flow;
            do{
                for(int i=0;i<=t;i++)
                    v[i]=0;
                flow=dinic(s,INF);
                f+=flow;
            }while(flow);
        }
        if(f==S)
            printf("%d\n",ans);
        else
            puts("-1");
    }
    return 0;
}