[省选前题目整理][BZOJ 2324][ZJOI 2011]营救皮卡丘(费用流)

本文详细解析了一个经典的最小费用最大流问题实例,通过构建特殊的网络流模型来求解旅行路线问题,确保每个节点被恰好访问一次的同时,使得总费用最小。文章介绍了如何通过拆分节点、设置边的容量与费用等手段建立模型,并使用SPFA算法进行求解。

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=2324

思路

题目有几个非常重要的地方:
1、每个点必须经过
2、经过点 i 前,1~ i1 这些点都必须经过

为了满足2,定义i到j的最短距离就是满足路径上每个点标号均 <=max(i,j) 的最短路径长度。

为了满足(1),我们就把每个点拆成两个点:入点和出点,每个点的入点和出点之间连容量为1,费用为-INF的边。而0点不需要拆。

S向0点连容量为 k ,费用为0的边,表示一共有 k 个人。而从0点向1到n的每个点的入点连容量为1,费用为0点到该点最短距离的边,从每个点的出点向T连容量为1,费用为0的边,对于原图上的可以到达的点对 (i,j)(i<j) 而言,从 i 的出点向j的入点连容量为1,费用为 ij 最短路的边。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXE 201000
#define MAXV 1000
#define INF 2000000

using namespace std;

typedef long long int LL;

int S,T;

struct edge
{
    int u,v,cap,w,next;
}edges[MAXE];

int head[MAXV],nCount=0;

void AddEdge(int U,int V,int C,int W)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].cap=C;
    edges[nCount].w=W;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

void add(int U,int V,int C,int W)
{
    AddEdge(U,V,C,W);
    AddEdge(V,U,0,-W);
}

int q[MAXE*2],pre[MAXV],inQueue[MAXV];
LL dist[MAXV];

bool SPFA()
{
    memset(pre,-1,sizeof(pre));
    memset(dist,0x3f3f3f3f,sizeof(dist));
    memset(inQueue,false,sizeof(inQueue));
    int h=0,t=1;
    q[h]=S;
    dist[S]=0;
    inQueue[S]=true;
    while(h<t)
    {
        int u=q[h++];
        inQueue[u]=false;
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(dist[u]+edges[p].w<dist[v]&&edges[p].cap) //!!!!!
            {
                dist[v]=dist[u]+edges[p].w;
                pre[v]=p;
                if(!inQueue[v])
                {
                    inQueue[v]=true;
                    q[t++]=v;
                }
            }
        }
    }
    return pre[T]!=-1;
}

LL MCMF()
{
    LL cost=0;
    while(SPFA())
    {
        //if(dist[T]>=INF) break;
        int flow=INF;
        for(int p=pre[T];p!=-1;p=pre[edges[p].u])
            flow=min(flow,edges[p].cap);
        for(int p=pre[T];p!=-1;p=pre[edges[p].u])
        {
            edges[p].cap-=flow;
            edges[p^1].cap+=flow;
        }
        cost+=flow*dist[T];
    }
    return cost;
}

int dis[MAXV][MAXV];

int main()
{
    int s=0; //小源点
    memset(head,-1,sizeof(head));
    memset(dis,0x3f3f3f3f,sizeof(dis));
    nCount=1;
    int n,m,K;
    scanf("%d%d%d",&n,&m,&K);
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        dis[u][v]=dis[v][u]=min(dis[u][v],w);
    }
    for(int i=1;i<=n;i++) dis[i][i]=0;
    S=MAXV-2,T=MAXV-1;
    for(int k=0;k<=n;k++) //dis[i][j=从i到j,只经过了标号小于等于i和j的点的最短路
        for(int i=0;i<=n;i++)
            for(int j=0;j<=n;j++)
                if(k<=i||k<=j)
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    add(S,s,K,0); //S向0号点连容量为K,费用为0的边,限制人数
    add(s,T,K,0);
    for(int i=1;i<=n;i++)
    {
        add(s,i,1,dis[s][i]); //S向每个点出点连容量为1,费用为0的边,补充流量
        add(i,i+n,1,-INF); //设费用为-INF强制走过该边
        add(i+n,T,1,0); //每个点入点向T连容量为1,费用为0,保证每个点都被访问了一次(这样做肯定满流)
    }
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++) //!!!!
            if(dis[i][j]<INF)
                add(i+n,j,1,dis[i][j]); //对于可以到达的两个点i、j之间,连容量为INF,费用为两点间最短路的边,表示这条路径可以走无数次,并且有费用。
    printf("%lld\n",MCMF()+n*INF);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值