网络流学习笔记

大佬command_block BLOG

最大流

引例

假定现在有一个无限放水的自来水厂和一个无限收水的小区,他们之间有多条水管和一些节点构成。
每一条水管有三个属性:流向,流量,容量。我们用(u,v)表示一条水管,这意味着水管中的水只能从u流向v,而不能从v流向u。流量即经过这条水管的单位时间内经过这条水管的水量。
我们将其模型化成为一个有向图,如下图所示,边上的数字即为水管的容量,流向用箭头来表示。当然,现在所有的水管流量都是0。
对于这一类型的有向图,我们称之为流网络。

现在有一个问题需要我们去解决:水厂在单位时间内最多能发送多少水给小区?
这就是网络流中的一个问题:最大流问题。

基本芝士

源点:发送流的节点。
汇点:接收流的节点。
弧:流网络图中的有向边,就是边
弧的流量:在一个流网络中,每一条边都有一个流量,即单位时间内流经该边的流的量。一般地,我们使用流量函数f(x,y)表示(x,y)的流量。
弧的容量:在一个流网络中,每一条边都会有一个容量限制,即边上流量的最大值。一般地,我们使用容量函数c(x,y)表示(x,y)的容量。
弧的残量:即每一条边的剩余容量,可以表示为c(x,y)-f(x,y)
容量网络:已知每一条边的容量的流网络即为容量网络
流量网络:已知每一条边的流量的流网络即为流量网络
残量网络:已知每一条边的残量的流网络即为残量网络。所有边的流量均为0的残量网络就是容量网络。    

性质

容量限制:∀(x,y)∈E,有0<=f(x,y)<=c(x,y)。
斜对称性:∀(x,y)∈E,有f(x,y)=−f(y,x)。
流量守恒:除了源点与汇点之外,流入任何节点的流一定等于流出该节点的流。

基本算法

直接介绍 dinic

最大流求解方法是找最短增广路更新残量网络,为了保证正确性每条边增加反向边进行反悔。

基于 EK算法实现单路增广,bfs 对图分层后进行多路增广

路径的信息更新: 每次从当前点出发,选用从当前点所在层到下一层的边,发送一定的流量,流量的大小取边残量和当前点从源点获取的剩余流中两者的最小值

搜索完成后,即不再有流能够往后发送,或者能够抵达汇点。此时返回一个流量值,即这条增广路的流量(若不再有流能够往后发送,则返回的流量值为0),此时就能够对边和反向边的残量进行更新了。

Dinic算法就完成了,其时间复杂度为O(n^2 *m)。

发现时间复杂度并不优秀,因为即使实现多路增广在dfs时都会从头开始,所以采用当前弧优化

在DFS的过程中,我们可能会多次经过一个点。我们会重复的处理一些边。 但是,在每次处理的过程中,已经处理完毕的边在这次 dfs 中不再有任何作用,一旦处理完毕,该边就废了。

所以,我们每次只需要记录当前处理的边的编号,下次经过这个点的时候,可以直接从这条边开始。

代码见例题

例题

例一 骑士共存问题 - 洛谷

补充:最小割

回到引例:“假定现在有一个无限放水的自来水厂和一个无限收水的小区,他们之间有多条水管和一些节点构成。”
现在得到了这些水管和节点的信息,试图破坏掉其中的一些水管,让小区一滴水都收不到。破坏每一条水管需要会产生一个疲劳值,假定产生的疲劳值等于水管的容量,那么请问在最优方案下会产生多少疲劳值。

Solution

网格图,考虑黑白染色(就是行列和奇偶)

发现同色点必不影响,故考虑异色点之间的影响

类似二分图,左部点为白色右部点黑色,考虑将不能共存的非障碍点连一个流量为 inf 的边

逆向思考求不能共存的点的最小数量即求最小割

ans = n * n - flow - m

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10,inf=1e10+10;
int n,head[N],idx=1,k;
int s,t;
struct edge{
	int v,next,w;
}e[N*2];
int dis[N],cur[N];
int a[220][220];
void add(int u,int v,int w){
	idx++;
	e[idx].v=v;
	e[idx].next=head[u];
	e[idx].w=w;
	head[u]=idx;
	
	idx++;
	e[idx].v=u;
	e[idx].w=0;
	e[idx].next=head[v];
	head[v]=idx;
}
int bfs(){
	queue<int> q;
	q.push(s);
	for(int i=0;i<=t;i++) dis[i]=inf;
	cur[s]=head[s];
	dis[s]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].v,w=e[i].w;
			if(dis[v]!=inf||w<=0) continue;
			dis[v]=dis[u]+1;
			cur[v]=head[v];
			if(v==t) return 1;
			q.push(v);
		}
	}
	return 0;
}
int dfs(int u,int fl){
	if(u==t) return fl;
	int used=0;
	for(int &i=cur[u];i&&fl;i=e[i].next){
		int v=e[i].v,w=e[i].w;
		if(dis[v]!=dis[u]+1||w<=0)continue;
		int k=dfs(v,min(fl,w));
		if(!k) {
			dis[v]=inf;
		}
		fl-=k;
		used+=k;
		e[i].w-=k;
		e[i^1].w+=k;
	}
	return used;
}
int ax[8]={2,1,2,-1,1,-2,-1,-2},ay[8]={1,2,-1,2,-2,1,-2,-1};
int id(int x,int y){
	return (x-1)*n+y;
}
inline int read(){
	int w=1,z=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') {
		if(ch=='-') w=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9') {
		z=z*10+ch-'0';
		ch=getchar();
	}
	return w*z;
}
signed main(){
	n=read(),k=read();
	s=0,t=n*n+1;
	for(int i=1;i<=k;i++){
		int u,v;
		u=read();
		v=read();
		a[u][v]=1;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(a[i][j]) continue;
			if((i+j)%2==1) {
				add(s,id(i,j),1ll);
			}
			else {
				add(id(i,j),t,1ll);
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if((i+j)%2==0||a[i][j]) continue;
			int x=id(i,j);
			for(int k=0;k<8;k++){
				int ii=i+ax[k],jj=j+ay[k];
				if(ii<1||ii>n||jj<1||jj>n||a[ii][jj]) continue;
				add(x,id(ii,jj),inf);
			}
		}
	}
	int sum=0;
	while(bfs()){
		sum+=dfs(s,inf);
	}
	printf("%lld",n*n-k-sum);
	return 0;
}

费用流

在最大流的基础上加一个费用,bfs换成 spfa 对图进行分层,其他不变

#include<bits/stdc++.h>
using namespace std;
const int N=5005,M=100005,inf=2e9+10;
int n,m,s,t;
struct edge{
	int v,next,w,p;
}e[M*2];
int head[M],idx=1;
void add(int u,int v,int w,int p){
	idx++;
	e[idx].v=v;
	e[idx].w=w;
	e[idx].p=p;
	e[idx].next=head[u];
	head[u]=idx;
	
	idx++;
	e[idx].v=u;
	e[idx].w=0;
	e[idx].p=-p;
	e[idx].next=head[v];
	head[v]=idx;
}
int dis[M],cur[M],vis[M];//vis是否被松弛 
bool bfs(){
	queue<int> q;
	for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
	dis[s]=0;
	q.push(s);
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next){
			int v=e[i].v,w=e[i].w,p=e[i].p;
			if(w<=0||dis[u]+p>=dis[v]) {
				continue;
			}
			dis[v]=dis[u]+p;
			if(vis[v]==0) vis[v]=1,q.push(v);
		}
		vis[u]=0;
	}
	return dis[t]<inf; 
}
int ans;
int dfs(int u,int fl){
	if(u==t||!fl) return fl;
	int used=0;
	vis[u]=1;
	for(int &i=cur[u];i&&fl;i=e[i].next){
		int v=e[i].v,w=e[i].w,p=e[i].p;
		if(vis[v]==1||dis[v]!=dis[u]+p||w<=0) continue;
		int k=dfs(v,min(fl,w));
		if(!k) {continue;}//
		ans+=k*p;
		e[i].w-=k,e[i^1].w+=k;
		used+=k,fl-=k;
	}
	vis[u]=0;
	if(!used) dis[u]=inf;
	return used;
}

signed main(){
	cin>>n>>m>>s>>t;
	for(int i=1;i<=m;i++){
		int u,v,w,z;
		cin>>u>>v>>w>>z;
		add(u,v,w,z);
	}
	int anss=0;
	while(bfs()){
		for(int i=1;i<=n;i++) cur[i]=head[i];
		anss+=dfs(s,inf);
	}
	cout<<anss<<" "<<ans<<endl;
	return 0;
}

上下界

是什么:上下界网络流在原先网络流流量上限 e[i].w 的基础上加上下限

基本思路:先实现可行流,即有流从原点流到汇点,以每条边的下界为流量建图

再在减去可行流流量的残余网络上跑最大流

无源汇上下界可行流

无源汇有上下界可行流

Question:

n 个点,m 条边,每条边 e 有一个流量下界 l(e)  和流量上界 r(e),求一种可行方案使得在所有点满足流量平衡条件的前提下,所有边满足流量限制。

Solution:

首先,下界必须流满,故以下界为流量建图,r[i]-l[i] 即为自由流,但显然,这样建边会使某些点收支不平衡(理解为入边权和!=出边权和) 就需要建立超级原点和超级汇点使得收支平衡。

考虑记录入边权和与出边权和的差为 sum[point] :有超级原点汇点 s,t

if sum[point] <0  : connect(s,point,-sum[point])

if sum[point] >0  : connect(point,t,sum[point])

在这个残余网络上跑dinic ,若最大流能满足收支不平衡空缺就有解

原网络的流量就相当于 初始流 + 附加流

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10,inf=3e9+100;
int n,m,s,t;
int idx=1,head[N],cur[N],dis[N];
struct edge{
	int v,next,w;
}e[N*2];
int l[N],r[N],su[N];
void add(int u,int v,int w){
	idx++;
	e[idx].v=v;
	e[idx].w=w;
	e[idx].next=head[u];
	head[u]=idx;
	
	idx++;
	e[idx].v=u;
	e[idx].w=0;
	e[idx].next=head[v];
	head[v];
}
bool bfs(){
	queue<int> q;
	for(int i=1;i<=t;i++){
		cur[i]=head[i];
		dis[i]=inf;
	}
	q.push(s);
	dis[s]=0;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next){
			if(e[i].w&&dis[e[i].v]==inf){
				dis[e[i].v]=dis[u]+1;
				q.push(e[i].v);
			} 
		}
	}
	return dis[t]<inf;
}
int dfs(int u,int fl){
	if(u==t) return fl;
	int used=0;
	for(int &i=cur[u];i;i=e[i].next){
		if(dis[e[i].v]==dis[u]+1&&e[i].w){
			int k=dfs(e[i].v,min(fl,e[i].w));
			if(k){
				e[i].w-=k;
				e[i^1].w+=k;
				used+=k;
				fl-=k;
				if(!fl) return used;
			}
			else dis[e[i].v]=inf;
		}
	}
	return used;
}
int dinic(){
	int ans=0;
	while(bfs()){
		ans+=dfs(s,inf);
	}
	return ans;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int a,b;
		cin>>a>>b>>l[i]>>r[i];
		add(a,b,r[i]-l[i]);
		su[a]-=l[i];
		su[b]+=l[i];
	}
	s=n+1,t=n+2;
	int res=0;
	for(int i=1;i<=n;i++){
		if(su[i]>0) {
			res+=su[i];
			add(s,i,su[i]);
		}
		if(su[i]<0) {
			add(i,t,-su[i]);
		}
	}
	int ret=dinic();
	if(ret<res) {
		cout<<"NO";
	}
	else{
		cout<<"YES";
		cout<<endl;
		for(int i=1;i<=m;i++) {
			cout<<r[i]-e[i*2].w<<endl;
		}
	}
	return 0;
}

有源汇上下界可行流

在无源汇基础上在 S T 连一条流为 inf 的边即可

可以理解为 电源内部电流从负极流向正极

有源汇上下界最大流

例题

有源汇上下界最大流

基于有源汇上下界可行流,可行流流量加上再跑一次最大流

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+10,inf=3e9+100;
int n,m,s,t,S,T;
int idx=1,head[N],cur[N],dis[N];
struct edge{
	int v,next,w;
}e[N*2];
int l[N],r[N],su[N];
void add(int u,int v,int w){
	//cout<<u<<v<<w<<endl;
	idx++;
	e[idx].v=v;
	e[idx].w=w;
	e[idx].next=head[u];
	head[u]=idx;
	
	idx++;
	e[idx].v=u;
	e[idx].w=0;
	e[idx].next=head[v];
	head[v]=idx;
}
queue<int> q;
bool bfs(){
	
	for(int i=1;i<=n+2;i++){
		cur[i]=head[i];
		dis[i]=0;
	}
	q.push(s);
	dis[s]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next){
			if(e[i].w&&dis[e[i].v]==0){
				dis[e[i].v]=dis[u]+1;
				q.push(e[i].v);
			} 
		}
	}
	return dis[t];
}
int dfs(int u,int fl){
	
	if(u==t) return fl;
	int used=0;
	for(int &i=cur[u];i;i=e[i].next){
		if(dis[e[i].v]==dis[u]+1&&e[i].w){
			int k=dfs(e[i].v,min(fl,e[i].w));
			if(k){
	//			cout<<u<<" "<<e[i].v<<endl;
				e[i].w-=k;
				e[i^1].w+=k;
				used+=k;
				fl-=k;
				if(!fl) return used;
			}
			else dis[e[i].v]=-1;
		}
	}
	return used;
}
int dinic(){
	int ans=0;
	while(bfs()){
		ans+=dfs(s,inf);
	}
	return ans;
}
signed main(){
	cin>>n>>m>>S>>T;
	for(int i=1;i<=m;i++){
		int a,b;
		cin>>a>>b>>l[i]>>r[i];
		add(a,b,r[i]-l[i]);
		su[a]-=l[i];
		su[b]+=l[i];
	}
	s=n+1,t=n+2;
	int res=0;
	for(int i=1;i<=n;i++){
		if(su[i]>0) {
			res+=su[i];
			add(s,i,su[i]);
		}
		if(su[i]<0) {
			add(i,t,-su[i]);
		}
	}
	add(T,S,inf);
	int ret=dinic();
	if(ret<res) {
		cout<<"please go home to sleep";
	}
	
	else{
		s=S,t=T;
		ret=dinic();
		cout<<ret;
		
	}
	return 0;
}

烦人魔板

Lg P5192

虚拟原点 S 、汇点 T,Pi 表示每天,Qi 表示少女 ,连这些边:

1、连 Qi 、T ,下限为 Gi ,上限为 inf

2、连 S 、Pi ,下限为 0 ,上限为 Di

3、对于每天 的对象 T (i , j) ,数量 [ L(i , j) , R(i , j) ] ,连 Pi 、T(i , j) ,上下界为 [l , r]

代码贴贴

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+10,inf=3e9+100;
int n,m,s,t,S,T,sum;
int idx=1,head[N],cur[N],dis[N];
struct edge{
	int v,next,w;
}e[N*2];
int l[N],r[N],su[N];
void add(int u,int v,int w){
	//cout<<u<<" "<<v<<" "<<w<<" "<<endl;
	idx++;
	e[idx].v=v;
	e[idx].w=w;
	e[idx].next=head[u];
	head[u]=idx;
	
	idx++;
	e[idx].v=u;
	e[idx].w=0;
	e[idx].next=head[v];
	head[v]=idx;
}
void con(int u,int v,int l,int r){
	su[u]-=l,su[v]+=l;
	add(u,v,r-l);
}
queue<int> q;
bool bfs(){
	
	for(int i=0;i<=n+m+10;i++){
		cur[i]=head[i];
		dis[i]=inf;
	}
	q.push(s);
	dis[s]=0;
	cur[s]=head[s];
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=e[i].next){
			//cout<<u<<" "
			if(e[i].w&&dis[e[i].v]==inf){
				dis[e[i].v]=dis[u]+1;
				cur[e[i].v]=head[e[i].v];
				q.push(e[i].v);
			} 
		}
	}
	return dis[t]<inf;
}
int dfs(int u,int fl){
//	cout<<u<<endl;
	if(u==t) return fl;
	int used=0;
	for(int &i=cur[u];i;i=e[i].next){
	//	cout<<u<<" "<<e[i].v<<endl;
		if(dis[e[i].v]==dis[u]+1&&e[i].w){
		//	cout<<u<<" "<<e[i].v<<endl;
			int k=dfs(e[i].v,min(fl,e[i].w));
			if(k){
	//			cout<<u<<" "<<e[i].v<<endl;
				e[i].w-=k;
				e[i^1].w+=k;
				used+=k;
				fl-=k;
				if(!fl) return used;
			}
			else dis[e[i].v]=inf;
		}
	}
	return used;
}
void clear(){
	idx=1;
	sum=0;
	for(int i=0;i<=n+m+10;i++) su[i]=head[i]=0;
	memset(su,0,sizeof su);
	memset(head,0,sizeof head);
}
int dinic(){
	int ans=0;
	while(bfs()){
		ans+=dfs(s,inf);
	}
	return ans;
}
signed main(){
	while(scanf("%lld%lld",&n,&m)!=EOF){
		clear();
		S=0,T=n+m+1;
		for(int i=1;i<=m;i++) {
			int x;
			cin>>x;
			con(S,i,x,inf);
		}
		for(int i=1;i<=n;i++){
			int c,d;
			cin>>c>>d;
			con(m+i,T,0,d);
			for(int j=1;j<=c;j++) {
				int x,l,r;
				cin>>x>>l>>r;
				con(x+1,m+i,l,r);
			}
		}
		s=m+n+2,t=m+n+3;
		for(int i=0;i<=m+n+1;i++){
			if(su[i]>0) add(s,i,su[i]),sum+=su[i];
			if(su[i]<0) add(i,t,-su[i]);
		}
		add(T,S,inf);
		int ret=dinic();
		if(ret<sum) {
			puts("-1\n");
			continue;
		}
		else{
			ret=e[idx].w;
			e[idx].w=e[idx-1].w=0;
			s=S,t=T;
			ret+=dinic();
			cout<<ret<<endl<<endl;
		}
	}
	return 0;
}

有源汇上下界最小流

板板

LOJ

基于有源汇上下界最大流,先跑可行流(判断超级源汇点是否满流),做完再删去 S 到 T 的边 ,反向跑最大流即退流(注意方向),用可行流减去退流即可

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10, inf = 3e9 + 100;
int n, m, s, t, S, T;
int idx = 1, head[N], cur[N], dis[N];
struct edge {
    int v, next, w;
} e[N * 2];
int l[N], r[N], su[N];
void add(int u, int v, int w) {
    idx++;
    e[idx].v = v;
    e[idx].w = w;
    e[idx].next = head[u];
    head[u] = idx;

    idx++;
    e[idx].v = u;
    e[idx].w = 0;
    e[idx].next = head[v];
    head[v] = idx;
}
queue<int> q;
bool bfs() {

    for (int i = 1; i <= n + 2; i++) {
        cur[i] = head[i];
        dis[i] = 0;
    }

    q.push(s);
    dis[s] = 1;

    while (!q.empty()) {
        int u = q.front();
        q.pop();

        for (int i = head[u]; i; i = e[i].next) {
            if (e[i].w && dis[e[i].v] == 0) {
                dis[e[i].v] = dis[u] + 1;
                q.push(e[i].v);
            }
        }
    }

    return dis[t];
}
int dfs(int u, int fl) {

    if (u == t)
        return fl;

    int used = 0;

    for (int &i = cur[u]; i; i = e[i].next) {
        if (dis[e[i].v] == dis[u] + 1 && e[i].w) {
            int k = dfs(e[i].v, min(fl, e[i].w));
            if (k) {

                e[i].w -= k;
                e[i ^ 1].w += k;
                used += k;
                fl -= k;

                if (!fl)
                    return used;
            } else
                dis[e[i].v] = -1;
        }
    }

    return used;
}
int dinic() {
    int ans = 0;

    while (bfs()) {
        ans += dfs(s, inf);
    }

    return ans;
}
signed main() {
    cin >> n >> m >> S >> T;

    for (int i = 1; i <= m; i++) {
        int a, b;
        cin >> a >> b >> l[i] >> r[i];
        add(a, b, r[i] - l[i]);
        su[a] -= l[i];
        su[b] += l[i];
    }

    s = n + 1, t = n + 2;
    int res = 0;

    for (int i = 1; i <= n; i++) {
        if (su[i] > 0) {
            res += su[i];
            add(s, i, su[i]);
        }

        if (su[i] < 0) {
            add(i, t, -su[i]);
        }
    }

    add(T, S, inf);
    int ret = dinic();

    if (ret < res) {
        cout << "please go home to sleep";
    }

    else {
    	ret = e[idx].w;
    	e[idx].w = e[idx-1].w = 0;
        s = T, t = S;//退流操作从汇点到原点
        ret -= dinic();
        cout << ret;

    }

    return 0;
}

例题1

清理雪道 - 洛谷

Solution

惯用思路考虑流是什么,,本题一个斜坡为一条边,题目要求遍历所有的边

流为这条边被清扫多少次,答案为最小流

所以想到上下界,一条边必须清扫故下界为1,上界不限为inf

飞机随便飞所有原点汇点连到所有点,注意不需要处理上下界

Code

    void add(int u, int v, int w) {
        idx++;
        e[idx].v = v;
        e[idx].w = w;
        e[idx].next = head[u];
        head[u] = idx;

        idx++;
        e[idx].v = u;
        e[idx].w = 0;
        e[idx].next = head[v];
        head[v] = idx;
      }
    void ad(int u,int v,int l,int r){
    	add(u,v,r-l);
	    su[v]+=l;
	    su[u]-=l;
    }
    cin >> n;
	S=n+1,T=n+2,s=n+3,t=n+4;
    for (int i = 1; i <= n; i++) {
        int a;
        cin >> a;
        for(int j=1,b;j<=a;j++) {
        	cin>>b;
        	ad(i, b, 1, inf);
		}
		add(S,i,inf),add(i,T,inf);
    }
    for (int i = 1; i <= n; i++) {
        if (su[i] > 0) {
            add(s, i, su[i]);
        }
        if (su[i] < 0) {
            add(i, t, -su[i]);
        }
    }
    add(T,S,inf);
    int ss=dinic();
    int ans=e[idx].w;
    e[idx].w=e[idx-1].w=0;
    s=T,t=S;
    cout<<ans-dinic();

例题2

士兵占领 - 洛谷

Solution

网格加障碍加行列至少放置求最小,可以想到这个思路

对于 n 行建 n 个点,m 列建 m 个点 ,钦定先行后列

将原点与行点连边,下界为 Li ,列点与汇点连边,下界为 Ci

考虑行列之间,若行列 ( i , j ) 无障碍即可到达,为了不重复放连限制为 [ 0 , 1 ] 的边

跑最小流即可

Code

int k;
    cin >> m >> n>>k;
    int M=m+n;
	S=M+1,T=M+2,s=M+3,tim=t=M+4;
    for (int i = 1; i <= m; i++) {
        int a;
        cin >> a;
		ad(S,i,a,inf);
    }
    for(int i=1;i<=n;i++) {
    	int a;
    	cin>>a;
    	ad(i+m,T,a,inf);
	}
	for(int i=1;i<=k;i++){
		int x,y;
		cin>>x>>y;
		ax[x][y]=1;
	}
	for(int i=1;i<=m;i++){
		for(int j=1;j<=n;j++) {
			if(!ax[i][j]) {
				ad(i,j+m,0,1);
			}
		}
	}
	int tot=0;
    for (int i = 1; i <= T; i++) {
        if (su[i] > 0) {
            add(s, i, su[i]);
            tot+=su[i];
        }
        if (su[i] < 0) {
            add(i, t, -su[i]);
        }
    }
    add(T,S,inf);
    int ss=dinic();
    if(ss<tot) {
    	cout<<"JIONG!";
    	return 0;
	}
    int ans=e[idx].w;
    e[idx].w=e[idx-1].w=0;
    s=T,t=S;
    cout<<ans-dinic();

上下界最小费用可行流

1、有源汇最小费用可行流

直接把前面的最大流换成费用流即可。注意要预先加上初始流的费用。

2、有源汇最小费用可行流

具体流程也差不多,但是无源汇的题目很有可能产生负环,需要用到上面那个带负环的费用流。

例题

[AHOI2014/JSOI2014] 支线剧情 - 洛谷

Solution

把支线剧情当边,要求遍历所有的边,点不可重复,想网络流

有走几次就花几次费用,想最小费用可行,发现覆盖所有边的特点,所以加入上下界

将上面的上下界可行流转换为费用流

考虑连边,俩个剧情点可达即可连 [1,inf] 的边

Code

signed main() {
    cin >> n;
	S=1,T=n+1,s=n+2,tim=t=n+3;
    for (int i = 1; i <= n; i++) {
        int a;
        cin >> a;
        for(int j=1,b,c;j<=a;j++) {
        	cin>>b>>c;
        	ans+=c;
        	su[b]++;
        	su[i]--;
        	add(i, b, inf-1, c);
		}
    }
    for(int i=2;i<=n;i++) {
    	add(i,T,inf,0);
	}
    for (int i = 1; i <= T; i++) {
        if (su[i] > 0) {
            add(s, i, su[i],0);
        }
        if (su[i] < 0) {
            add(i, t, -su[i],0);
        }
    }
    add(T,S,inf,0);
    int ss=dinic();
    cout<<ans;

    return 0;
}

补充

最小路径覆盖问题

最小路径覆盖问题 - 洛谷

就是用 n 条简单路覆盖整个图所有点,路之间不可共点

Solution1:

其实是二分图最大匹配

把每个点拆成两个放两边(但实际上不需要拆),连边左边只出右边只入

发现左边剩下的点即为每条路的终点,右边剩下的为每条路的起点,所以

ans = 点数 - 二分图最大匹配数

路径方案可利用 匈牙利算法match数组,u = match[v] 意味着 u->v 的边

Tips: 图为  DAG

Solution2

利用网络流,其实也是跑二分图最大匹配,拆点连流量为1的边,跑 dinic

方案用一个并查集维护,在残量网络上跑即可,具体见代码

Code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+10,inf = 1e10+10;
int head[N],idx=1;
int f[N];
int findd(int x){
	if(x==f[x]) return x;
	return f[x]=findd(f[x]);
}
void merge(int x,int y){
	int fx=findd(x),fy=findd(y);
	if(fx!=fy) f[fx]=fy;
}
signed main(){

	cin>>n>>m;
	t=n*2+1;
	for(int i=1;i<=m;i++){
		int u,v,w;
		cin>>u>>v;
		con(u,v+n,1);
	}
	for(int i=1;i<=n;i++){
		con(s,i,1);
		con(i+n,t,1);
		f[i]=i,f[i+n]=i+n;
	}
	int sum=0;
	while(bfs()){
		sum+=dfs(s,inf);//删去了板子部分
	}
	for(int i=1;i<=n;i++){
		for(int j=head[i];j;j=e[j].next){
			int v=e[j].v,w=e[j].w;
			if(!w&&v-n<=n&&v-n>0&&v) {//没流量即为跑到了,v>n 才是正向边
				merge(i,v-n);
			}
		}
	}
	for(int i=n;i;i--){
		int flag=0;
		for(int j=1;j<=n;j++) {
			if(findd(j)==i) {
				cout<<j<<" ";
				flag=1;
			}
		}
		if(flag) puts("");
	}
	cout<<n-sum;
	return 0;
}

上面是不可共点,若可共点呢?

我们先对原图进行一次闭包传递(Floyd)即可,把可达的两点连边再跑不可共点的就行了

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值