这道题和最小路径覆盖比较像,都是把点拆成x部和y部,最小路径覆盖直观地看可以说是在y部中给x部的点找后继,这个题直观地看可以说是在x部中给y部的点找前驱。以下建模方法摘自学长题解
思路和最小路径覆盖类似,先进行拆点,把每个点u拆成u和u‘。
对于跳跃模式【忘了叫什么模式了】就从源点往u'连一条流量为1,费用为边权的边。
对于星球间的航道(u,v)【假设u<v】就从u往v'连一条流量为1,费用为边权的边。
从源点往每一个点u连一条流量为1,费用为0的边,从每个点u’往汇点连一条流量为1,费用为0的边。
这样保证每个点都能被经过。流量为1保证了每个点最多被经过一次。
然后跑一遍最小费用最大流,输出费用即可。
这样的话对于每个y部的点,流入它的满流的边对应的起点就是它的前驱。这个建模方法由于y部向T边流量为1,S向x部流量也为1,保证了每个点最多只有一个前驱,最多只有一个后继。至于跳跃,可以把它看成从一个点免费跳到源点再有代价跳到目标点。
还有就是因为vector实在是太慢最近正在习惯用邻接表写网络流,因为反边的性质边要从偶数开始存,然而我还习惯h[]初始值全部=0,这个错误会忽略一条边,在弱的样例中不容易发现,以后还是从2开始存吧
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define INF (2100000000)
#define fr(i,s,t) for (i=s;i<=t;i++)
using namespace std;
struct edge{
int next,from,to,cap,flow,cost;
edge(){}
edge (int x,int x1,int x2,int x3,int x4,int x5):
next(x),from(x1),to(x2),cap(x3),flow(x4),cost(x5){}
}q[60010];
int n,m,h[4010],T,m1=1,p[4010],a[4010],d[4010];
bool inq[4010];
void addedge(int x,int y,int cost){
q[++m1]=edge(h[x],x,y,1,0,cost);
h[x]=m1;
q[++m1]=edge(h[y],y,x,0,0,-cost);
h[y]=m1;
}
bool SPFA(int &cost){
memset(p,0,sizeof(p));
memset(a,0,sizeof(a));
memset(inq,0,sizeof(inq));
int x,y,i;
a[0]=INF; inq[0]=1;
fr(i,1,T) d[i]=INF;
queue <int> Q;
Q.push(0);
while (!Q.empty()){
x=Q.front(); Q.pop();
inq[x]=0;
for (i=h[x];i;i=q[i].next){
if (q[i].cap==q[i].flow) continue;
y=q[i].to;
if (d[x]+q[i].cost>=d[y]) continue;
d[y]=d[x]+q[i].cost;
p[y]=i;
a[y]=min(a[x],q[i].cap-q[i].flow);
if (!inq[y]){
inq[y]=1;
Q.push(y);
}
}
}
if (d[T]==INF) return 0;
cost+=d[T]*a[T];
for (i=T;i;i=q[p[i]].from){
q[p[i]].flow+=a[T];
q[p[i]^1].flow-=a[T];
}
return 1;
}
int main(){
int i,x,y,z,cost=0;
scanf("%d %d",&n,&m);
fr(i,1,n) scanf("%d",&x),addedge(0,n+i,x);
fr(i,1,m){
scanf("%d %d %d",&x,&y,&z);
if (x>y) swap(x,y);
addedge(x,n+y,z);
}
T=2*n+1;
fr(i,1,n){
addedge(0,i,0);
addedge(n+i,T,0);
}
while (SPFA(cost));
cout<<cost;
}