图论6:网络流

网络流

定义

N = ( V , E , c , X , Y ) N=(V,E,c,X,Y) N=(V,E,c,X,Y)是一个网络

1. G ( V , E ) G(V,E) G(V,E)为点集为 V V V边集为 E E E的有向图

2. c c c E E E上的非负函数,称为容量函数,对于每条边 e e e, c ( e ) c(e) c(e)表示边 e e e的容量

3. X , Y X,Y X,Y为两个非空不交子集, X X X为发点集, X X X中点为源, Y Y Y为收点集, Y Y Y中点为汇, X , Y X,Y X,Y之外点集为中间点。(多源多汇可转化为单源单汇,下面均讨论单源单汇问题, s s s, t t t为源汇)

4. f f f为流量函数, f ( e ) f(e) f(e)表示边 e e e的流量,流的价值等于源的出流量等于汇的入流量。

5. A ⊆ V , A ′ = V − A , s ∈ A , t ∈ A ′ , ( A , A ′ ) A \subseteq V,A'=V-A,s \in A,t \in A',(A,A') AV,A=VA,sA,tA,(A,A)中的边集称为 N N N的一个割。

性质

1.容量限制, 0 ≤ f ( e ) ≤ c ( e ) 0 \leq f(e) \leq c(e) 0f(e)c(e)

2.流量守恒,中间点入流等于出流,源的出流等于汇的入流。

3.最大流最小割定理:网络流的最大流等于最小割。

证明:
​ 1)任意一个割大于任意一个流,即任意割大于最大流。根据流量守恒,总流量=A中的出流量和=A’中入流量和<=(A,A’)中边权和=割。

​ 2)存在一个割等于最大流。若存在增广路则未达到最大流,则最大流时不存在增广路,此时(X,Y)之间边集即为最小割。

算法

EK

算法原理:残量网络中不存在增广路时达到最大流。

证明:

​ 1.显然,存在增广路时未达到最大流。

​ 2.不存在增广路时,残量网络中s和t不连通,即(X,Y)间边集为一个割,由流量守恒得也为一个流,根据最大流最小割定理,任意割大于等于最大流,即若割等于流,则流为最大流。

算法过程:不断bfs找到最短的增广路进行增广,直到不存在增广路为止。

时间复杂度: O(V*E^2)

证明:

​ 1)每次增广后,任意点到源点的距离不减。

​ 反证:dis(x)为增广前x到源点的距离,dis’(x)为增广后到源点的距离。第一次有点到源点距离减少时,设

​ v为增广后距源点最近的距离减少的点,即dis’(v)<dis(v)。

​ 设dis’(v)=dis’(u)+1,即dis’(u)<dis’(v),则dis’(u)>=dis(u), dis’(v)>=dis(u)+1

​ 若增广前存在边(u,v),则dis(v)<=dis(u)+1 即dis’(v)>=dis(v),矛盾

​ 若增广前不存在边(u,v),则此次增广流过边(v,u),即dis(u)=dis(v)+1,即dis’(v)>=dis(v)+2,矛盾

​ 2)设一条增广路上残量最小的边为关键边,一次增广至少经过一条关键边。

​ 3)一条关键边经增广后即消失,设边(u,v)成为关键边时,dis(v)=dis(u)+1,(u,v)重新出现时,即增广路经过(v,u),

​ 设此时dis’(u)=dis’(v)+1,由1)知dis’(u)=dis’(v)+1>=dis(v)+1=dis(u)+2,即u到源点距离至少增加2。所以每条边最

​ 多成为关键边V/2次,共E×V/2条关键边,至多増广E×V/2。每次増广bfs复杂度为O(E),总复杂度O(V*E^2)。

Dinic

EK中每次bfs后沿到汇点距离最小的路径増广,然而距离最小的路径并不一定只有一条,每次都重新bfs很浪费,于是思考能不能支持多路径増广。

每次bfs后,用dfs沿着最短路径一直増广直到不存在增广路,这样可以把到汇点距离最短的所有路径全部(重新分层后所有最短路不减,显然可以全部)増广(每次増广至少减少一条边,每条增广路最长为V,时间复杂度为V*E。),下次bfs时到汇点的最短路径一定会增加,那么最多bfs V次,总复杂度为V^2×E.

单位流量时复杂度为O(E*sqrt(V))

ISAP

之前一直写非递归版没有发现,其实ISAP就是加了优化的Dinic,递归版几乎一样。

Dinic中每次重新分层时其实改变的地方并不多,有时改动很小还是要花大工夫bfs整个图,很不划算。我们只在最开始bfs整张图分层,且从t开始标号,然后在dfs増广的同时对分层进行改变。当此次dfs使当前点和Y集合断开时,给当前点选一个有边相连而标号最小的与Y相连的点的标号+1作为新标号即可。

P3376 【模板】网络最大流

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=200007;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m,s,t;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct edge {
	int fr,to,cap,fl,nx;
}e[N];
int fir[N],ecnt=1;
void add(int u,int v,int w) {
	e[++ecnt]=(edge){u,v,w,0,fir[u]}; fir[u]=ecnt;
	e[++ecnt]=(edge){v,u,0,0,fir[v]}; fir[v]=ecnt;
}

queue<int>que;
int d[N],cur[N],c[N];
void bfs(int s,int t) {
	For(i,1,n) d[i]=n;
	d[t]=0;
	que.push(t);
	while(!que.empty()) {
		int x=que.front();
		que.pop();
		for(int i=fir[x];i;i=e[i].nx) if(e[i].cap==0) {
			int y=e[i].to;
			if(d[y]==n) {
				d[y]=d[x]+1;
				que.push(y);
			}
		}
	}
}

int pr[N];
#define inf 1e18
LL calc(int s,int t) {
	LL fl=inf;
	for(int x=t;x!=s;x=e[pr[x]].fr) 
		fl=min(fl,(LL)e[pr[x]].cap-e[pr[x]].fl);
	for(int x=t;x!=s;x=e[pr[x]].fr)
		e[pr[x]].fl+=fl,e[pr[x]^1].fl-=fl;
	return fl;
}

LL isap(int s,int t) {
	For(i,0,n) c[i]=0;
	bfs(s,t);
	For(i,1,n) cur[i]=fir[i],c[d[i]]++;	
	LL rs=0;
	for(int x=s;d[x]<n;) {
		if(x==t) {
			rs+=calc(s,t);
			x=s;
		}
		int ok=0,D=n;
		for(int &i=cur[x];i;i=e[i].nx) if(d[e[i].to]+1==d[x]&&e[i].cap>e[i].fl) {
			ok=1; pr[x=e[i].to]=i; break;
		}
		if(!ok) {
			cur[x]=fir[x];
			for(int i=fir[x];i;i=e[i].nx) if(e[i].cap>e[i].fl)
				D=min(D,d[e[i].to]+1);
			if(!(--c[d[x]])) break;
			c[d[x]=D]++;
			if(x!=s) x=e[pr[x]].fr;
		}	
	}
	return rs;
}

int main() {
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	read(n); read(m); read(s); read(t);
	For(i,1,m) {
		int u,v,w;
		read(u); read(v); read(w);
		add(u,v,w);
	}
	printf("%lld\n",isap(s,t));
    Formylove;
}

最小费用最大流

用EK算法实现最小费用最大流,把bfs换成spfa,复杂度的证明可以参考EK算法。
证明:当原图不存在负费用圈时,每次通过最短路更新得到的一定是当前流量下的最小费用。

​ 数学归纳法,设f(x)表示流量为x时的一个最小费用流且f(x)不存在负费用圈。f(0),即原图不存在负费用圈。

​ 当已知f(x),设f(x+1)是f(x)的基础上沿最短路増广,f(x+1)-f(x)即为一条从s到t的路径。若是存在f’(x+1)使f’(x+1)小于f(x+1),在网络f’(x+1)-f(x)中,除了源汇出入流为1,其余点出入流为0,即一条从s到t的路径加若干圈。因为f’(x+1)<f(x+1),即f(x)中存在负费用圈,与题设矛盾。得证。

其他:当前流为最小费用流,当且仅当残量网络中不存在负费用圈。

P3381 【模板】最小费用最大流
把spfa换成dijkstra可提高效率。给每人一个势能函数d,每次d+=上次的最短路,使边权变为w(u,v)+d(u)-d(v)。具体可见模板里的题解。类似的方法可以做全源最短路。

//Achen
#include<bits/stdc++.h>
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Rep(i,a,b) for(int i=(a);i>=(b);i--)
#define Formylove return 0
const int N=100007;
typedef long long LL; 
typedef double db;
using namespace std;
int n,m,s,t;

template<typename T> void read(T &x) {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct edge {
	int fr,to,cap,fl,cost,nx;
}e[N];
int fir[N],ecnt=1;
void add(int u,int v,int w,int co) {
	e[++ecnt]=(edge){u,v,w,0,co,fir[u]}; fir[u]=ecnt;
	e[++ecnt]=(edge){v,u,0,0,-co,fir[v]}; fir[v]=ecnt;
}

#define inf 1e9
LL d[N],sd[N];
struct node {
	int x; LL d;
	friend bool operator <(const node&A,const node&B) {
		return A.d>B.d;
	}
};
priority_queue<node>que;
int pr[N],vis[N];
int spfa(int s,int t) {
	For(i,1,n) d[i]=inf;
	d[s]=0;
	que.push((node){s,0});
	while(!que.empty()) {
		node t=que.top();
		que.pop(); int x=t.x;
		if(d[x]!=t.d) continue;
		for(int i=fir[x];i;i=e[i].nx) if(e[i].cap>e[i].fl) {
			int y=e[i].to;
			if(d[y]>d[x]+e[i].cost+sd[x]-sd[y]) {
				d[y]=d[x]+e[i].cost+sd[x]-sd[y];
				pr[y]=i;
				que.push((node){y,d[y]});
			}
		}
	}
	return d[t]!=inf;
}

LL rs1,rs2;
void calc(int s,int t) {
	LL fl=inf,co=0;
	for(int x=t;x!=s;x=e[pr[x]].fr) 
		fl=min(fl,(LL)e[pr[x]].cap-e[pr[x]].fl);
	for(int x=t;x!=s;x=e[pr[x]].fr)
		e[pr[x]].fl+=fl,e[pr[x]^1].fl-=fl;
	rs1+=fl; rs2+=(d[t]-sd[s]+sd[t])*fl;
}

void EK(int s,int t) { 
	For(i,1,n) sd[i]=0;
	while(spfa(s,t)) {
		calc(s,t);
		For(i,1,n) sd[i]+=d[i];
	}
	printf("%lld %lld\n",rs1,rs2);
}

int main() {
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	read(n); read(m); read(s); read(t);
	For(i,1,m) {
		int u,v,w,co;
		read(u); read(v); read(w); read(co);
		add(u,v,w,co);
	} 
	EK(s,t);
    Formylove;
}

有上下界的网络流

无源汇可行流

要求一个满足上下界的可行解,先强行让所有下界流过去,但此时不满足流量守恒,我们再来构造一个守恒的合法的解。(u,v)下界为dn,则建立超级源ss向v连容量为dn的边,u向超级汇tt连容量为dn的边,跑ss,tt最大流,若新建边全满流,则存在可行解。若为费用流,则原边保留原费用,新建边费用为0即可。

有源汇的可行流

因为s和t不满足流量守恒,添加边(t,s)容量为 i n f inf inf,再做无源汇的可行流即可。

有源汇的最大/小流

最大流:构造出可行流后,再在残量网络上跑s,t最大流即可,此时中间点始终满足流量守恒且符合上下界。

最小流:构造出可行流后,再在残量网络上跑t,s最大流,即尽可能地退流即可。

常见模型

二分图最小割模型

题目:

BZOJ 2127 happiness

三分图最小割模型:

题目:

bzoj4625: [BeiJing2016]水晶

最大权闭合图

定义一个有向图的闭合图是该有向图的一个点集,且该点集的所 有出边都还指向该点集。闭合图不一定是一个连通块。

给每个点分配一个权值,权值和最大的闭合图被称为最大权闭合图。

转化为最小割模型,对于原图中的边(u,v)连边(u,v)容量为 i n f inf inf,源点向权值为正的点连边容量为 v v v,权值为负的点向汇点连边容量为 − v -v v

显然割边一定是与源或汇相连的边,且容量为 i n f inf inf的边限制了两点必须在同一个集合,和源在一个集合的点即为所选点, a n s = ∑ v ≥ 0 , v ∈ X v − ∑ v &lt; 0 , v ∈ X ( − v ) = ∑ v ≥ 0 v − ∑ v ≥ 0 , v ∈ Y v − ∑ v &lt; 0 , v ∈ X v = ∑ v ≥ 0 v − c u t ( X , Y ) ans=\sum_{v \geq 0,v \in X}v-\sum_{v&lt;0,v \in X}(-v)=\sum_{v \geq 0}v-\sum_{v \geq 0,v \in Y}v-\sum_{v&lt;0,v \in X}v=\sum_{v \geq 0}v-cut(X,Y) ans=v0,vXvv<0,vX(v)=v0vv0,vYvv<0,vXv=v0vcut(X,Y)

最大密度子图

无向图的子图的密度: D = ∣ E ∣ ∣ V ∣ D=\frac{|E|}{|V|} D=VE

分数规划问题,设 g ( λ ) = ∣ E ∣ − λ ∣ V ∣ g(\lambda)=|E|-\lambda|V| g(λ)=EλV,二分 λ \lambda λ,求 g g g.

​ 法1.边拆成点,权值为1,点权值为 − λ -\lambda λ,选边的条件是两个点都必须选,转化为最大权闭合图,图大小为

( E + V , E + V ) (E+V,E+V) (E+V,E+V) ,复杂度较高。

​ 法2. m a x ( ∣ E ∣ − λ ∣ V ∣ ) = − m i n ( λ ∣ V ∣ − ∣ E ∣ ) = − m i n ( ∑ v ∈ X λ − ∑ v ∈ X d − c u t ( X , Y ) 2 ) max(|E|-\lambda|V|)=-min(\lambda|V|-|E|)=-min(\sum_{v \in X}\lambda-\frac{\sum_{v \in X}d-cut(X,Y)}{2}) max(EλV)=min(λVE)=min(vXλ2vXdcut(X,Y))

= − 1 2 ∗ m i n ( ∑ v ∈ X ( 2 λ − d ) + c u t ( X , Y ) ) =-\frac{1}{2}*min(\sum_{v \in X}(2\lambda-d)+cut(X,Y)) =21min(vX(2λd)+cut(X,Y))

​ 这一步就转化为最基本的文理分科模型, s s s-> v : 0 v:0 v:0,v: v v v-> t : 2 λ − d t:2\lambda-d t:2λd, u u u-> v v v:1, v v v-> u u u:1。为避免出现负数,给与

​ 源汇相连的边容量都加上一个很大的正数 U U U

最大密度子图带边权的推广

只需把度数 d d d换成关联边的边权和,原图中的边容量为原边权即可。此时二分的复杂度会偏高。

最大密度子图带边权和点权的推广

D = ∑ V a l v + ∑ V a l e ∣ V ∣ D=\frac{\sum Val_v+\sum Val_e}{|V|} D=VValv+Vale

m a x ( ∑ V a l v + ∑ V a l e − λ ∣ V ∣ ) = − m i n ( λ ∣ V ∣ − ∑ V a l v − ∑ V a l e ) = − m i n ( ∑ v ∈ X ( λ + V a l x ) − ∑ v ∈ X d − c u t ( X , Y ) 2 ) max( \sum Val_v+\sum Val_e-\lambda|V|)=-min(\lambda|V|-\sum Val_v-\sum Val_e)=-min(\sum_{v \in X}(\lambda+Val_x)-\frac{\sum_{v \in X}d-cut(X,Y)}{2}) max(Valv+ValeλV)=min(λVValvVale)=min(vX(λ+Valx)2vXdcut(X,Y))

同理前两种模型。

二分图相关

1.二分图中最大流等于最大匹配数

2.二分图最小顶点覆盖等于最大匹配

3.二分图最大独立集=所有顶点数-最小顶点覆盖=最小边覆盖

4.二分图最小路径覆盖数=点数-最小路径覆盖中的边数=点数-最大匹配

网络流求解混合图欧拉回路

基于流量守恒的网络流模型。

存在欧拉回路的条件是所有点入度等于出度,先将所有无向边任意定向,出度大于入度的点缺少入度,从源向该点连容量为(出度-入度)/2的边,出度小于入度的点缺少出度,从其向汇连容量为(入度-出度)/2的边,原本定向(u,v)的边连u->v容量为1,一条从源到汇流经(u,v)的流代表改变边(v,u)的方向,此时u的入度增加,v的出度增加。故当所有点连向源汇的边满流时有解,满流的中间边即为需要改变方向的边。

poj1637Sightseeing tour

最小割树

有结论,无向图任意两点间的最小割只有n-1个。
每次取两点做最小割,最小割把点集分成两个集合,用这个最小割更新集合间的最小割,再对每个集合递归处理。
没有证明,这篇写的东西太多啊宸实在证不下去了。
2229: [Zjoi2011]最小割

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
const int N=157,inf=0x7fffffff;
typedef long long LL;
using namespace std;
int T,n,m,a[N],b[N],dis[N][N];

template<typename T>void read(T &x)  {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

struct edge {
    int u,v,fl,cap,nx;
    edge(){}
    edge(int u,int v,int fl,int cap,int nx):u(u),v(v),fl(fl),cap(cap),nx(nx){} 
}e[N*N]; 

int fir[N],cur[N],ecnt,p[N];
void add(int u,int v,int cap) {
    e[++ecnt]=edge(u,v,0,cap,fir[u]); fir[u]=ecnt;
    e[++ecnt]=edge(v,u,0,0,fir[v]); fir[v]=ecnt;
}

int calc(int s,int t) {
    int fl=inf;
    for(int i=t;i!=s;i=e[p[i]].u) 
        fl=min(fl,e[p[i]].cap-e[p[i]].fl);
    for(int i=t;i!=s;i=e[p[i]].u) 
        e[p[i]].fl+=fl,e[p[i]^1].fl-=fl;
    return fl;
} 

int d[N],c[N];
queue<int>que;
int bfs(int s,int t) {
    que.push(t);
    d[t]=0;
    while(!que.empty()) {
        int x=que.front();
        que.pop();
        for(int i=fir[x];i;i=e[i].nx) if(d[e[i].v]==n&&e[i].cap==0) {
            d[e[i].v]=d[x]+1;
            que.push(e[i].v);
        }
    }
}

int ISAP(int s,int t) {
    memset(c,0,sizeof(c));
    for(int i=1;i<=n;i++) d[i]=n,cur[i]=fir[i];
    bfs(s,t);
    for(int i=1;i<=n;i++) c[d[i]]++;
    int res=0;
    for(int x=s;d[x]<n;) {
        if(x==t) {
            res+=calc(s,t);
            x=s;
        }
        int ok=0;
        for(int &i=cur[x];i;i=e[i].nx) if(d[e[i].v]+1==d[x]&&e[i].cap>e[i].fl) {
            p[x=e[i].v]=i; ok=1; break;
        }
        if(!ok) {
            cur[x]=fir[x]; int M=n;
            for(int i=fir[x];i;i=e[i].nx) if(e[i].cap>e[i].fl) 
                M=min(M,d[e[i].v]+1);
            if(!(--c[d[x]])) break;
            c[d[x]=M]++;
            if(x!=s) x=e[p[x]].u; 
        }
    }
    return res;
}

int vis[N];
void dfs(int x) {
    vis[x]=1;
    for(int i=fir[x];i;i=e[i].nx) if(!vis[e[i].v]&&e[i].fl<e[i].cap) 
        dfs(e[i].v);
}

void restore() { for(int i=2;i<=ecnt;i++) e[i].fl=0; }


void solve(int l,int r) {
    if(l>=r) return;
    restore();
    int t=ISAP(a[l],a[r]),L=l-1,R=r+1;
    memset(vis,0,sizeof(vis));
    dfs(a[l]);
    for(int i=1;i<=n;i++) if(vis[i]) 
        for(int j=1;j<=n;j++) if(!vis[j]) 
            dis[i][j]=dis[j][i]=min(dis[i][j],t);
    for(int i=l;i<=r;i++) 
        if(vis[a[i]]) b[++L]=a[i];
        else b[--R]=a[i];
    for(int i=l;i<=r;i++) a[i]=b[i]; 
    solve(l,L); solve(R,r);
}

int main() {
    read(T);
    while(T--) {
        ecnt=1;
        memset(fir,0,sizeof(fir));
        memset(dis,127/3,sizeof(dis));
        read(n); read(m);
        for(int i=1;i<=m;i++) {
            int u,v,w;
            read(u); read(v); read(w);
            add(u,v,w);
            add(v,u,w);
        }
        for(int i=1;i<=n;i++) a[i]=i;
        solve(1,n);
        int q,xx;
        read(q);
        while(q--) {
            read(xx);
            int ans=0;
            for(int i=1;i<=n;i++)
                for(int j=i+1;j<=n;j++)
                    if(dis[i][j]<=xx) ans++; 
            printf("%d\n",ans);
        }
        puts(""); 
    }
    return 0;
}
/*
1
5 0
1
0
*/

全局最小割

题目
同上,又是一个我只会背板的东西。

//Achen
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<vector>
#include<cstdio>
#include<queue>
#include<cmath>
const int N=507,inf=0x7fffffff;
typedef long long LL;
using namespace std;
int n,m,eg[N][N];

template<typename T>void read(T &x)  {
    char ch=getchar(); x=0; T f=1;
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0'; x*=f;
}

int vis[N],dis[N],id[N];
int stoer_wagner(int n) {
    int res=inf;
    for(int i=0;i<n;i++) id[i]=i;
    while(n>1) {
        memset(vis,0,sizeof(vis));
        memset(dis,0,sizeof(dis));
        int pr=0;
        vis[id[pr]]=1;
        for(int i=1;i<n;i++) {     
            int k=-1;
            for(int j=1;j<n;j++) if(!vis[id[j]]) {
                dis[id[j]]+=eg[id[pr]][id[j]];
                if(k==-1||dis[id[j]]>dis[id[k]]) k=j;
            }
            vis[id[k]]=1;
            if(i==n-1) {
                res=min(res,dis[id[k]]);
                for(int j=0;j<n;j++) {
                    eg[id[pr]][id[j]]+=eg[id[k]][id[j]];
                    eg[id[j]][id[pr]]+=eg[id[j]][id[k]];
                }
                id[k]=id[--n];
            }
            pr=k;
        }
    }
    return res;
}

int main() {
    while(scanf("%d%d",&n,&m)==2) {
        memset(eg,0,sizeof(eg));
        for(int i=1;i<=m;i++) {
            int x,y,z;
            read(x); read(y); read(z);
            eg[x][y]+=z; eg[y][x]+=z;
        }
        printf("%d\n",stoer_wagner(n));
    }
    return 0;
}

参考资料

算法导论p 426~427

运筹学课件 最大流与最小费用流

胡伯涛《最小割模型在信息学竞赛中的应用

Lynstery《最小割模型在信息学竞赛中的应用》学习笔记

liu_runda 有上下界的网络流学习笔记

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值