最大流
引例
假定现在有一个无限放水的自来水厂和一个无限收水的小区,他们之间有多条水管和一些节点构成。
每一条水管有三个属性:流向,流量,容量。我们用(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;
}
烦人魔板
虚拟原点 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;
}
有源汇上下界最小流
板板
基于有源汇上下界最大流,先跑可行流(判断超级源汇点是否满流),做完再删去 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、有源汇最小费用可行流
具体流程也差不多,但是无源汇的题目很有可能产生负环,需要用到上面那个带负环的费用流。
例题
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)即可,把可达的两点连边再跑不可共点的就行了