=== ===
这里放传送门
=== ===
题解
首先可以想到的是用最大流来模拟这个过程,然后在不同的行走方法上加上不同的费用来得到最小花费。那么就是先把每个点拆成两个,然后如果第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;
}
偏偏在最后出现的补充说明
用最大流来模拟问题是网络流问题的一个常见思路。
一定要对于常用模型的原理有正确的认识