常见费用流:最小/最大费用最大流
要求:先保证最大流再保证费用最小/大
做法:贪心
每次找出单位费用最小的交错路更新即可
将dinic算法中的bfs改为spfa,每次走dis[p]=dis[x]+w的边转移(保证了走的是费用最小的交错路)
记录每个节点经过与否:有可能这个节点不能到达T,dinic时候会一直进入这个点,打上标记保证每个节点只经过一遍
bool get_dis(){
queue<int>q;
memset(dis,0x3f3f3f3f,sizeof(dis)),memset(in,false,sizeof(in));
dis[p_beg]=0;
q.push(p_beg),in[p_beg]=true;
while(!q.empty()){
int x=q.front();
q.pop();
in[x]=false;
for(int i=0;i<tu[x].size();i++){
int p=tu[x][i].p,vid=tu[x][i].vid,w=tu[x][i].w;
if(!v[vid]) continue;
if(dis[p]>dis[x]+w){
dis[p]=dis[x]+w;
if(!in[p]) q.push(p),in[p]=true;
}
}
}
return dis[p_end]<inf/10;
}
int dinic(int x,int flow){
if(x==p_end) return flow;
int res=flow;
vis[x]=true;
for(int i=las[x];i<tu[x].size();i++){
las[x]=i;//当前弧优化:以前更新过的是无用状态,不必再次更新
int p=tu[x][i].p,vid=tu[x][i].vid,w=tu[x][i].w;
if(dis[p]!=dis[x]+w||vis[p]||!v[vid]) continue;
int now=dinic(p,min(res,v[vid]));
v[vid]-=now,v[vid^1]+=now;
res-=now,min_cost+=w*now;
if(!res) break;
}
return flow-res;
}
例题
1.航班安排
将每个请求拆点,一个代表开始另一个代表结束
设立一个虚拟源点,连向每个初始状态容量1费用-w[1][beg]的边,初始状态连上结尾状态容量1费用为所得报酬的边,结束节点连汇点容量1费用-w[1][beg]的边
每次完成一个认为可以接另一个,如果满足条件连另一个初始节点
最后源点连虚拟源点容量为飞机架数的边
跑最大费用最大流
2.晨跑
消耗体力看为价值,每条路径容量为1(只能经过一次),直接最小费用(最少消耗的体力)最大流(最大循环)
要拆店并连上容量1的边,因为每个点只经过一次
3.修车
考虑求总花费
从顾客向师傅连边
把每个师傅划为多个状态,每个状态代表后面还有多少人等待修车
故连接这个状态的边的花费是修车花费时间*等待人数
每条边容量都是1,每个师傅状态连汇点容量1花费0的边
每个师傅使用的状态一定会是联系的,若不连续无法达到最小花费,故此方法正确
4.订货
显然是餐巾问题的变形,但此时可以dp求解
将式子拆开,与k有关的可以前缀和维护
代码