无源汇带上下界的可行流
n个点,m条边,每条边有一个流量下界和流量上界,求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。
分析:
首先假设每条边的流过的流量为其下界,如果能流量平衡,那么就存在可行解。但是存在着流量不平衡的情况,所以我们需要调整每个边的流量,使得流量平衡。
那么每条边可以调整的流量即为上界减去下界,意义就在于若这条边有流量,则最后的可行流会多出这些流量,这样保证最后的可行流不会超出上界。
然后对于每个点我们维护其下界情况下的in与out流量。若一个点in[i]>out[i],那么说明在大家的流量都为最低值时,这个点的流量为out。那么我们就需要从s连一条到i的边,流量为in[i]-out[i]。那么若这条出边满流,i的连出边会有in[i]-out[i]的流量,那么这时i的流量就为in[i]了,这样就平衡了。
同理in[i]<out[i]时就连一条边从i到e,流量为差值。
所以存在可行解的话,也就是从s到e跑最大流。流量为所有连着s的边权和,即把s跑满了,因为一条边贡献了一个点的入值,另一个点的出值,所以总的值是为0的,所以e连着的边权和是等于s的。即这样也跑满了e。
这时输出方案的话,就是下界值加上跑完Dinic后的流量(即反边的权值) 。
/*
步骤:
1.对于每条边,流量为上界减去下界
2.维护每个点的in与out,这个值为下界值
3.判断in与out的关系,连源点或汇点
3.跑最大流,判断是否有解
4.根据反边输出可行流
*/
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 205;
struct edges{
int to,next;
ll val;
}edge[300005];
int head[maxn],cnt;
int dep[maxn];
int cur[maxn];
int s,e;
int tot;
void add(int u,int v,ll val)
{
edge[cnt].to = v;
edge[cnt].val = val;
edge[cnt].next = head[u];
head[u] = cnt++;
}
bool bfs()
{
queue<int> q;
memset(dep,0,sizeof(dep));
dep[s] = 1;
q.push(s);
while( !q.empty() )
{
int x = q.front();
q.pop();
for (int i = head[x]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if( edge[i].val > 0 && dep[v] == 0 )
{
dep[v] = dep[x] + 1;
q.push(v);
if( v == e ) return true;
}
}
}
if( dep[e] == 0 ) return false;
return true;
}
ll dfs(int x,ll flow)
{
if( x == e ) return flow;
ll used = 0;
for (int i = cur[x]; i != -1; i = edge[i].next)
{
cur[x] = i;
if( dep[edge[i].to] == dep[x] + 1 && edge[i].val > 0 )
{
ll d = dfs(edge[i].to,min(flow-used,edge[i].val));
edge[i].val -= d;
edge[i^1].val += d;
used += d;
if( used == flow ) break;
}
}
if( used == 0 ) dep[x] = -1;
return used;
}
ll Dinic()
{
ll ans = 0;
while( bfs() )
{
for (int i = 0; i <= tot; i++)
cur[i] = head[i];
ans += dfs(s,1e18);
}
return ans;
}
ll in[maxn],out[maxn];
ll ans[300005];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
memset(head,-1,sizeof(head));
int n,m;
cin >> n >> m;
s = 0,e = n + 1;
tot = n + 1;
for (int i = 1; i <= m; i++)
{
int x,y,lower,upper;
cin >> x >> y >> lower >> upper;
ans[cnt] = lower; //当前边的流量为下界
add(x,y,upper-lower),add(y,x,0); //差值建图
out[x] += lower,in[y] += lower; //维护in,out数组
}
ll sum = 0; //求和与起点的边权
for (int i = 1; i <= n; i++)
{
if( in[i] > out[i] ) //若in大于out,则连一条起点的边
{
sum += in[i]-out[i];
add(s,i,in[i]-out[i]),add(i,s,0);
}else if( in[i] < out[i] ) add(i,e,out[i]-in[i]),add(e,i,0); //小于连汇点
}
ll val = Dinic();
if( val != sum ) cout << "NO" << '\n'; //没跑满,意味着无法调整
else
{
cout << "YES" << '\n';
for (int i = 0; i < cnt; i += 2) //加上反边的贡献
{
if( edge[i].to != e && edge[i^1].to != s )
{
cout << ans[i] + edge[i^1].val << '\n';
}
}
}
return 0;
}
有源汇带上下界的可行流
n个点,m条边,有一个去起点,一个终点。每条边有一个流量下界和流量上界,求一种可行方案使得除起点与终点的所有点满足流量平衡条件的前提下,所有边满足流量限制。
分析:由于起点与终点不需要流量平衡,而且从起点流出的流量一定等于流入终点的流量,那么从终点向起点连一条下界为0(没有要求),上界为无穷大(通过这条边来平衡流量)。这样就变成了无源汇带上下界的可行流问题了。即加上这条边后,我们要求起点与终点也需要流量平衡。
有源汇带上下界的最大流
与可行流一样,但是现在需要求出最大流,使得每条边都满足流量限制。
分析:首先我们要先保证可行流,在有可行流的基础上。我们在残量网络上,以初始的起点与终点,删去我们初始时从源点到汇点的那条边,再跑一次最大流,那么可行流加上这次的最大流,即为最后要求的最大流。
由于我们建的网络是边可调整的网络,在这个残量网络上跑最大流,意味着那些边在保证可行流的基础上,还能通过增加流过的流量,使得网络平衡,不超上界且达到最大的额外流。
对于如何找到可行流,思考由于所有的点在第一次Dinic后都流量平衡了,但是要注意这个网络是可调整网络,它的每条边去掉了下界,正常的边的流量只是意味了它在下界的基础上增加了这些流量。那么我们会发现,在处理源点与汇点时,我们增加了一条从汇点到源点的下界为0,上界为无穷大的边,那么这条边反边的权值不就是可行流了吗?因为源点只有这条边为入边,且这条边的下界为0,那么这个的值就是在可行流时流入源点的流量,即为可行流。
/*
步骤:
1.先跑有源汇上下界可行流,判断是否有解
2.利用初始汇点到源点的那条边,找到可行流的流量
3.删去初始汇点到源点的边
4.用初始源点和初始汇点跑最大流,加上可行流即为答案
*/
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 205;
struct edges{
int to,next;
ll val;
}edge[300005];
int head[maxn],cnt;
int dep[maxn];
int cur[maxn];
int s,e;
int tot;
void add(int u,int v,ll val)
{
edge[cnt].to = v;
edge[cnt].val = val;
edge[cnt].next = head[u];
head[u] = cnt++;
}
bool bfs()
{
queue<int> q;
memset(dep,0,sizeof(dep));
dep[s] = 1;
q.push(s);
while( !q.empty() )
{
int x = q.front();
q.pop();
for (int i = head[x]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if( edge[i].val > 0 && dep[v] == 0 )
{
dep[v] = dep[x] + 1;
q.push(v);
if( v == e ) return true;
}
}
}
if( dep[e] == 0 ) return false;
return true;
}
ll dfs(int x,ll flow)
{
if( x == e ) return flow;
ll used = 0;
for (int i = cur[x]; i != -1; i = edge[i].next)
{
cur[x] = i;
if( dep[edge[i].to] == dep[x] + 1 && edge[i].val > 0 )
{
ll d = dfs(edge[i].to,min(flow-used,edge[i].val));
edge[i].val -= d;
edge[i^1].val += d;
used += d;
if( used == flow ) break;
}
}
if( used == 0 ) dep[x] = -1;
return used;
}
ll Dinic()
{
ll ans = 0;
while( bfs() )
{
for (int i = 0; i <= tot; i++)
cur[i] = head[i];
ans += dfs(s,1e18);
}
return ans;
}
ll in[maxn],out[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
memset(head,-1,sizeof(head));
int n,m,s1,t1;
cin >> n >> m >> s1 >> t1;
s = 0,e = n + 1;
tot = n + 1;
for (int i = 1; i <= m; i++)
{
int x,y,lower,upper;
cin >> x >> y >> lower >> upper;
add(x,y,upper-lower),add(y,x,0); //差值建图
out[x] += lower,in[y] += lower; //维护in,out数组
}
ll sum = 0; //求和与起点的边权
for (int i = 1; i <= n; i++)
{
if( in[i] > out[i] ) //若in大于out,则连一条起点的边
{
sum += in[i]-out[i];
add(s,i,in[i]-out[i]),add(i,s,0);
}else if( in[i] < out[i] ) add(i,e,out[i]-in[i]),add(e,i,0); //小于连汇点
}
int index = cnt; //记录初始汇点到初始源点的边的编号
add(t1,s1,1e18),add(s1,t1,0); //从初始汇点到初始源点建一条下界为0,上界为无穷大的边
if( Dinic() != sum ) cout << "please go home to sleep" << '\n';
else
{
ll ans1 = edge[index^1].val; //利用反边找到可行流
edge[index].val = edge[index^1].val = 0; //删去这条边
s = s1,e = t1; //改为初始源点与初始汇点
ll ans2 = Dinic(); //残量网络上跑一次最大流
cout << ans1 + ans2 << '\n';
}
return 0;
}
有源汇上下界最小流
与可行流一样,但是现在需要求出最小流,使得每条边都满足流量限制。
分析:一样的我们还是需要求出可行流,在可行流的基础,尝试减少一些流量。如何才能产生减少流量的效果呢?我们考虑反向边,跑反向边的流量不就是减少正向边的流量吗。所以只要在上下界最大流的最后,把源点改为初始汇点,汇点该为初始源点,跑最大流,把可行流减去最大流即可。