有上下界的含义: 每条管道的流量必须在范围 [L,R] 内,即在普通最大流问题上增加了下界限制。
主要分为无源点汇点和有源点汇点两类问题。
1.无源汇点有上下界的最大流
此类问题一般为:给n个点,及m根pipe,每根pipe是用来流淌液体的,单向的,每时每刻每根pipe流进来的物质要等于流出去的物质,要使得m条pipe组成一个循环体,里面流淌物质。并且满足每根pipe一定的流量限制,范围为[Li,Ri].即要满足每时刻流进来的不能超过Ri(最大流问题),同时最小不能低于Li。
解决:普通的最大流可以视作下界为0,上界为容量。所以此时让所有管道的下界变为0,上界变为R-L。但是这样的话流量不守恒(流入的不等于流出的),需要更改建图方法,寻找附加流来满足守恒。
此时添加超级源点s和汇点t,定义du[MAXN]来记录每个节点的流量情况。
du[i] = in[i] (i节点所有入流下界之和) - out[i] (i节点所有出流下界之和).
当du[i]大于0时,从 s 到 i 连一条流量为du[i]的边;
当du[i]小于0时,从 i 到 t 连一条流量为-du[i]的边;
最后对(s,t)求最大流即可,求出的最大流为附加最大流,此时每条管道的实际流量还要加上之前为了改造而修改的下界L。
当附加流最大流==所有的du[]>0之和时,有可行解(可行流)(可以使流量守恒)。
//Dinic
#include<bits/stdc++.h>
#define ll long long
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define DEBUG cout<<",here\n";
#define MAXN 250
using namespace std;
struct EDGE
{
ll c;//capacity
ll flow;
EDGE():c(0),flow(0){}
};EDGE a[MAXN][MAXN];
vector<ll>link[MAXN];
vector<pair<ll,ll> >pipe;
ll ceng[MAXN];
//注意dn[]记录每条边的下界信息,不能用点的MAXN,不然会数组太小
ll du[MAXN],dn[MAXN*MAXN*2];
ll n,m,s,t,u,v,l,c,maxn,ans=0;
ll bfs()//最大流:构建层级网络,一次bfs就行了,很简单
{
memset(ceng,0,sizeof(ceng));
queue<ll>q;q.push(s);
ceng[s]=1;
while(!q.empty()){
ll now=q.front();q.pop();
for(auto& i:link[now]){
if(ceng[i]==0 && a[now][i].c-a[now][i].flow>0){
ceng[i]=ceng[now]+1;
q.push(i);
}
}
}
if(ceng[t])return 1;//最大流:能构建,继续之后的dfs
return 0;//最大流:不能构建更多了,没有更多的增广路了
}
//最大流:有当前弧优化
ll dfs(ll now,ll minflow)
{
if(now==t){ans+=minflow;return minflow;}
ll ret=0,minflow2;
for(auto& i:link[now]){
if(ceng[i]==ceng[now]+1 && a[now][i].c-a[now][i].flow>0){
minflow2=dfs(i,min(minflow,a[now][i].c-a[now][i].flow));
if(minflow2==0)ceng[i]=0;//debug
a[now][i].flow+=minflow2;
a[i][now].flow-=minflow2;
minflow-=minflow2;//最大流:剩余容量减少,以便从该节点直接再往后找增广路,提高效率,当前弧优化
ret+=minflow2;//最大流:dfs找到的最小剩余容量之和
if(minflow==0)break;
}
}
return ret;//最大流:返回从这个节点开始之后找到的所有增广路的流量之和
}
int main()
{
IOS
maxn=pow(2,31);
cin>>n>>m;
for(ll i=1;i<=m;i++){
cin>>u>>v>>l>>c;
a[u][v].c+=c-l;
du[u]-=l;du[v]+=l;dn[i-1]=l;//dn[]记录每条边的下界信息
link[u].push_back(v);
link[v].push_back(u);
pipe.push_back({u,v});
}
s=0;t=n+1;
ll totdu=0;
for(ll i=1;i<=n;i++){
if(du[i]>0){
link[s].push_back(i);
a[s][i].c+=du[i];
}
else if(du[i]<0){
link[i].push_back(t);
a[i][t].c+=-du[i];
}
if(du[i]>0)totdu+=du[i];//用于判断是否有可行解
}
while(bfs()){
dfs(s,maxn);
}
if(ans!=totdu){cout<<"NO\n";}
else{
cout<<"YES\n";
for(ll j=0;j<m;j++){
auto &i=pipe[j];
ll tmp=0;
tmp+=a[i.first][i.second].flow+dn[j];//每条边的实际流量=容量下界+附加流
cout<<tmp<<"\n";
}
}
return 0;
}
2.有源汇点有上下界的最大流
源点 s ,汇点 t 。
同样,因为有下界,所以先判断是否有可行流,可参考无源汇点的可行流判断。但有源汇点如何转化为无源汇的循环流图?增设一条从 t 到 s 没有下界且容量为无穷的边 即可。之后再添加超级源汇点ss和tt,和无源汇点一样判断可行流。
当有可行流时,再原封不动地以 s,t 为源汇点跑一次最大流,结果即为最终答案。
有源汇上下界最大流只需要求解可行流并判断可行后,原封不动地进行一次最大流,这个最大流就是答案(不用再加之前的可行流流量)。因为超级源汇的边全部满流不会影响,而原可行流流量全部在t-s的反向边上,跑最大流时刚好会算进去,这样跑出来的最大流就是最终答案。
//Dinic
#include<bits/stdc++.h>
#define ll long long
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define DEBUG cout<<",here\n";
#define MAXN 1505
using namespace std;
struct EDGE
{
ll c;//capacity
ll flow;
EDGE():c(0),flow(0){}
};EDGE a[MAXN][MAXN];
vector<ll>link[MAXN];
ll ceng[MAXN];
//无源汇:注意dn[]记录每条边的下界信息,不能用点的MAXN,不然会数组太小
ll du[MAXN];
ll g,C,D;
ll n,m,u,v,ans=0;
const ll INF=2147483648;
struct Dinic{
ll s,t;
const ll INF=2147483648;
void addEdge(ll u,ll v,ll c){
a[u][v].c+=c;
link[u].push_back(v);
link[v].push_back(u);
}
ll bfs()//最大流:构建层级网络,一次bfs就行了,很简单
{
memset(ceng,0,sizeof(ceng));
queue<ll>q;q.push(s);
ceng[s]=1;
while(!q.empty()){
ll now=q.front();q.pop();
for(auto& i:link[now]){
if(ceng[i]==0 && a[now][i].c-a[now][i].flow>0){
ceng[i]=ceng[now]+1;
q.push(i);
}
}
}
if(ceng[t])return 1;//最大流:能构建,继续之后的dfs
return 0;//最大流:不能构建更多了,没有更多的增广路了
}
//最大流:有当前弧优化
ll dfs(ll now,ll minflow)
{
if(now==t){return minflow;}
ll ret=0,minflow2;
for(auto& i:link[now]){
if(ceng[i]==ceng[now]+1 && a[now][i].c-a[now][i].flow>0){
minflow2=dfs(i,min(minflow,a[now][i].c-a[now][i].flow));
if(minflow2==0)ceng[i]=0;//debug
a[now][i].flow+=minflow2;
a[i][now].flow-=minflow2;
minflow-=minflow2;//最大流:剩余容量减少,以便从该节点直接再往后找增广路,提高效率,当前弧优化
ret+=minflow2;//最大流:dfs找到的最小剩余容量之和
if(minflow==0)break;
}
}
return ret;//最大流:返回从这个节点开始之后找到的所有增广路的流量之和
}
ll maxFlow(ll s,ll t,ll minflow){
this->s=s;this->t=t;
ll tmp=0;
while(bfs()){
tmp+=dfs(s,minflow);
}
return tmp;
}
};
Dinic solver;
int main()
{
while(cin>>n)
{
cin>>m;
ll dn=0;ans=0;
ll s=0,t=n+m+1,ss=n+m+2,tt=n+m+3;
for(ll i=0;i<=n+m+5;i++){
du[i]=0;link[i].clear();ceng[i]=0;
for(ll j=0;j<=n+m+5;j++){a[i][j].c=0;a[i][j].flow=0;}
}
/*例题特殊建图,不具代表性*/
for(ll i=1;i<=m;i++){
cin>>g;
solver.addEdge(n+i,t,INF-g);
du[n+i]-=g;du[t]+=g;
}
for(ll i=1;i<=n;i++){
cin>>C>>D;
solver.addEdge(s,i,D);
for(ll j=1;j<=C;j++){
ll T,L,R;
cin>>T>>L>>R;T+=1;
solver.addEdge(i,n+T,R-L);
du[i]-=L;du[n+T]+=L;dn+=L;//有下界就要更改du[]
}
}
/**/
solver.addEdge(t,s,INF);//添加t到s的无穷容量边
ll totdu=0;
for(ll i=1;i<=t;i++){
if(du[i]>0){
solver.addEdge(ss,i,du[i]);
}
else if(du[i]<0){
solver.addEdge(i,tt,-du[i]);
}
if(du[i]>0)totdu+=du[i];//无源汇:用于判断是否有可行解
}
ans=solver.maxFlow(ss,tt,INF);
if(ans!=totdu){cout<<"-1\n\n";}
else{
//有源汇上下界最大流只需要求解可行流并判断可行后,
//原封不动地进行一次最大流,
//这个最大流就是答案(不用再加之前的可行流流量)。
//因为超级源汇的边全部满流不会影响,
//而原可行流流量全部在t-s的反向边上,
//跑最大流时刚好会算进去,这样跑出来的最大流就是最终答案。
ans=solver.maxFlow(s,t,INF);
cout<<ans<<"\n\n";
}
}
return 0;
}