[BZOJ1927][Sdoi2010]星级竞速(费用流)

本文介绍了一种使用最小费用最大流算法解决特定路径寻找问题的方法。通过将问题转化为网络流问题,文章详细阐述了如何构造图模型,并利用二分图匹配原理确保正确计算费用。此外,还探讨了两种避免非最优路径的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

=== ===

这里放传送门

=== ===

题解

首先可以想到的是用最大流来模拟这个过程,然后在不同的行走方法上加上不同的费用来得到最小花费。那么就是先把每个点拆成两个,然后如果第i个点可以到达第j个点,就从第i个点的入点到第j个点的出点连一条费用为路径长度的边。那么关键就是如何处理那个跳跃的问题。

可以发现这样做的关键在于利用二分图的匹配,那么二分图匹配为什么能正确的解决这个问题呢?以下面这个图为例:
这里写图片描述
比如上面这个图跑出了两个匹配:a1-c2,c1-d2。这种匹配方案对应的实际情况应该是它先跳跃到a,然后从a跑到c再从c跑到d。也就是说这个时候从源点到a1的那条边记费用了,但从源点到c1的那条边没有记费用。出现这种情况的原因就是c对应的出点c2已经到达过了,也就是其实c这个点已经可以通过费用为0的路径到达了,当然不用再对它计算跳跃的费用了。而a对应的出点a2没有被访问过,也就是相当于直接跳跃到了a1,就要计算费用。

这说明如果从源点S跑到了一个点的入点,它算不算一次跳跃取决于这个点的出点有没有被访问过。那么如果一开始假设所有点都是通过跳跃到达的,那么只要访问了一个点的出点就相当于这个点不需要通过跳跃到达了,就要把相应的费用加回来。那么从S向所有点的入点连费用为0的边,从所有点的出点向T连费用为 vali 的边,其中 vali 是跳跃到点i需要的费用。中间那些边就连费用为 wij 的边就可以了。

但是这样还是会有一个问题。费用流是在保证最大流的前提下求最小费用的,那有没有可能图中还存在一条从a到b的路径,但不走这条路径反而更优?其实是有可能的。解决这个问题的方法有两个,第一个是在SPFA增广的时候只要总费用为正数,也就是会增加花费,那么就退出,因为已经找不到费用更小的增广路了;第二个是从每个点的入点向T连一条费用为0的边,也就是如果这个点直接跳跃比任何一种通过航路到达的方法都要优,那么它会直接走这条0权边。下面的代码这两个方法都写进去了,但实测这两种措施加入任何一个都可以过。

实际上这个题还有一种正确性更加显然的方法。。就是直接从S到入点连费用为0的边,S到出点连费用为 vali 的边,出点到汇点连费用为0的边,然后中间的点之间连航行费用的边。建出图来长得很像。。应该是因为思路基本上是一样的吧。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 1000000000
#define inc(x)(x=(x==2000)?1:x+1)
using namespace std;
int n,m,a[810],tot,p[2010],dis[2010],pre[2010],S,T,sum,Cost,q[2010],head,tail;
bool ext[2010];
struct edge{
    int to,flw,cst,nxt;
}e[100010];
void add(int from,int to,int flow,int cost){
    e[tot].to=to;e[tot].flw=flow;e[tot].cst=cost;
    e[tot].nxt=p[from];p[from]=tot++;
}
int Increase(int S,int T){
    int Min=inf;
    for (int i=T;i!=S;i=e[pre[i]^1].to)
      Min=min(Min,e[pre[i]].flw);
    for (int i=T;i!=S;i=e[pre[i]^1].to){
        e[pre[i]].flw-=Min;
        e[pre[i]^1].flw+=Min;
    }
    return Min;
}
bool SPFA(int &Cost){
    int dlt;
    memset(dis,127,sizeof(dis));
    memset(ext,false,sizeof(ext));
    head=0;tail=1;q[tail]=S;
    dis[S]=0;ext[S]=true;
    while (head!=tail){
        int u;inc(head);
        u=q[head];ext[u]=false;
        for (int i=p[u];i!=-1;i=e[i].nxt){
            int v=e[i].to;
            if (e[i].flw>0&&dis[v]>dis[u]+e[i].cst){
                dis[v]=dis[u]+e[i].cst;pre[v]=i;
                if (ext[v]==false){
                    inc(tail);q[tail]=v;ext[v]=true;
                }
            }
        }
    }
    if (dis[T]>0) return false;//如果下面加入了0权边,这里判断改成inf也是对的
    dlt=Increase(S,T);
    Cost+=dlt*dis[T];
    return true;
}
int main()
{
    scanf("%d%d",&n,&m);
    memset(p,-1,sizeof(p));
    S=0;T=2*n+1;
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i]);sum+=a[i];
        add(S,i,1,0);add(i,S,0,0);
        add(i,T,1,0);add(T,i,0,0);//这一句和上面dis<0的判断任意保留一个即可
        add(i+n,T,1,-a[i]);add(T,i+n,0,a[i]);
    }
    for (int i=1;i<=m;i++){
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        if (x<y){
            add(x,y+n,1,w);add(y+n,x,0,-w);
        }else{add(y,x+n,1,w);add(x+n,y,0,-w);}
    }
    while (SPFA(Cost));
    printf("%d\n",sum+Cost);
    return 0;
}

偏偏在最后出现的补充说明

用最大流来模拟问题是网络流问题的一个常见思路。
一定要对于常用模型的原理有正确的认识

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值