昨天学了下最大流,今天早晨看了看费用流,
其中最大流经常用于求二分图最大匹配及最短路,费用流的题经常需要添加一个超级源点及超级汇点,人为设置流量及费用(流量一般为1,费用一般为最短路径)将原来的不知道什么题转化为费用流。
有上下界无源汇可行流问题
一般的,定义一个网络是一个加权的有向图G=(V, E, C), E中的每条弧(u, v)都有一个容量上界C(u, v)≥0。
如果人为的规定V中的两个点s和t,其中s没有入度而t没有出度;并为E中的每条弧(u, v)赋予一个值f(u,v)≥0, f满足以下两个条件:
①除s,t之外的任意点都满足:
②任意一条 E中的弧(u, v),都满足f(u,v)≤c(u, v)
则称f是G的一个可行流,称s为流的源且t是流的汇。前一个条件被称为流量平衡条件,而后者则是容量限制条件。
而如果一个可行流f使原点提供的流量达到最大,则称f是G网络的最大流。
如果为G中的每条边再加入一个容量下界:令G=(V,E,B,C), B(u, v)表示弧(u, v)的容量下界。这样G就是一个容量有上下界的流网络。类似的定义G中的可行流f:
①除s,t之外的任意一个点 i都满足:
②任意一条E中的弧(u,v)都满足B(u, v)≤f(u,v)≤C(u, v)。
在一个有上下界的流网络G中,不设源和汇,但要求任意一个点i都满足流量平衡条件。
且每条边满足容量限制的条件下,寻找一个可行流f,或指出这样的可行流不存在,这种问题为无源汇的可行流,此类题的图中有环,也被称作循环流;
仔细分析一下,由于普通最大流中对每条边中流量的约束条件仅仅是f(u,v)≥0,而在这个问题中,流量却必须大于等于某个下界。因此可以想到,设
f(u,v)= B(u,v)+g(u, v) (*)
其中g(u,v)≥0,这样就可以保证f≥B,同时为了满足上界限制,有g(u,v) ≤C(u,v)-B(u, v)。
令g(u,v)=C(u, v)- B(u,v)。则大致可以将g看作无上界流网络C中的一个可行流。(修改原图中每条边的流量下界为0,上界为原上界-原下界,视为该边现在已经拥有了等同于该边流量下界的基础流量了)
但这样直接转化显然是不对的,由于每条边在原图中的流量下界不同,导致他们现在的基础流量不同;于是如果再让现在图中每个点出入相等,则表面相等,实则不同;如图:
红色边即为我们从原图中分离出的下界流量,黑色图是我们的新图,为了让新图也能满足流量平衡条件,要新建一个源点和一个汇点,设一点的红色边(流过该点的下界)总流量值为d(假设流出为正,流入为负)
如果d大于零,那么新图中该点流量要平衡还需流出大小为d的流量,那么建立该点到汇点流量为d的一条边,d小于零时相反,建立源点到该点流量为d的边
最后的图(图画的有的丑意思有就行。。)
算法:
- 按上述方法构造新网络(分离必要弧,附加源汇)
- 求附加源x到附加汇y的最大流
- 若x的出弧或y的入弧都满,则有解,将必要弧合并回原图;否则,无解。
若附加边满流,则跑出了一组可行流,原图中每条边的可行流是他在现在图中对应边的流量加流量下界
待解决:跑出的可行流有特性吗
CSP链接:http://118.190.20.162/view.page?gpid=T84
一个无源汇可行最小费用流问题,判断可行流的时候附加边费用视为0求出跑最小费用流即可
参考:https://wenku.baidu.com/view/0f3b691c59eef8c75fbfb35c.html
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll base=233;
const int MAXN=1e5+10;
#define pi acos(-1.0)
#define INF 0x3f3f3f3f
#define mod 1e9+7
#define lson rt<<1,l,mid
#define endll printf("\n")
#define pii pair<int, int>
#define rson rt*2+1,mid+1,r
#define s1(a) scanf("%d",&a)
#define stop system("pause")
#define lowbit(x) ((x)&(-x))
#define s2(a,b) scanf("%d %d",&a,&b)
#define mem(a,b) memset(a,b,sizeof(a))
#define s3(a,b,c) scanf("%d %d %d",&a,&b,&c)
struct Graph
{
int tot,head[MAXN];
struct IN{int v,next,w,c;}edge[MAXN<<1];
void init(){mem(head,-1);tot=0;}
void addedge(int u,int v,int w,int c)
{
edge[tot].v=v;edge[tot].w=w;
edge[tot].c=c;edge[tot].next=head[u];
head[u]=tot++;
//
edge[tot].v=u;edge[tot].w=0;
edge[tot].c=-c;edge[tot].next=head[v];
head[v]=tot++;
}
int Start(int u){return head[u];}
int To(int i){return edge[i].v;}
int Val(int i){return edge[i].w;}
int Cost(int i){return edge[i].c;}
int Next(int i){return edge[i].next;}
void Res(int i,int v){edge[i].w-=v;}
}G;
int n,m,e;//每边花费
int s,t;
int sum;//建立新图中源点的总流出值
int add;//建新图的时候删去的下界总流量
int du[MAXN];//某点向外流出的附加边总值与流进的附加边总权值之差
void input()
{
s2(n,m);G.init();
mem(du,0);add=0;
for(int i=0;i<m;i++)
{
int u,v;char tt[2];
scanf("%d%d%s",&u,&v,tt);
char t=tt[0];
if(t=='A')//A上界INF下界1,为了消除下界影响建立附加边u-->汇点-->源点-->v
{
du[u]--;du[v]++;//u点加上下界值(流出
add+=e;//统计总的减去的下界流量值
G.addedge(u,v,INF,e);//
}
else if(t=='B')//B上界1下界1
{
du[u]--;du[v]++;
add+=e;//(0,0)的边不用建了
}
else if(t=='C')//下界0上界INF
G.addedge(u,v,INF,e);
else if(t=='D')//上界1下界1
G.addedge(u,v,1,e);
}
//添加附加边
s=0,t=n+1,sum=0;//用于判断是否满足源点满流
for(int i=1;i<=n;i++)
{
if(du[i]>0)//需要添加流入的边
sum+=du[i],G.addedge(s,i,du[i],0);
else if(du[i]<0)
G.addedge(i,t,-du[i],0);
}
}
int flow[MAXN];//
int vis[MAXN],dis[MAXN];//用于SPFA
int pre[MAXN],rec[MAXN];//用于回溯
int SPFA(int s,int t)
{
mem(vis,0);mem(dis,INF);
queue<int>q;q.push(s);
dis[s]=0;flow[s]=INF;
while(!q.empty())
{
s=q.front();q.pop();vis[s]=0;
for(int i=G.Start(s);~i;i=G.Next(i))
{
int v=G.To(i);
if(G.Val(i)<=0) continue;//不存在增广路
if(dis[v]>dis[s]+G.Cost(i))
{
dis[v]=dis[s]+G.Cost(i);
pre[v]=s;rec[v]=i;
flow[v] = min(flow[s],G.Val(i));
if(!vis[v]) vis[v]=1,q.push(v);
}
}
}
if(dis[t]==INF) return -1;
return flow[t];
}
int mcmf(int s,int t)
{
int maxflow=0,mincost=0,minflow;
while((minflow=SPFA(s,t))!=-1)
{
maxflow+=minflow;
mincost+=dis[t]*minflow;
for(int k=t;k!=s;k=pre[k])
G.Res(rec[k],minflow),G.Res(rec[k]^1,-minflow);
}
if(maxflow!=sum) return -1;
return mincost+add;
}
int main()
{
int T,S;
s3(T,S,e);
while(T--)
{
input();
printf("%d\n",mcmf(s,t));
}
//stop;
}