WR:网流之神的传奇一生

\(\LARGE\text{“}\) 采菊东篱下,波撼岳阳城。\(\large {_”}\)

试见过,拟阵之交,因神的剑术沸腾爆裂。

试见过,资本之恶,因神的力量化雪纷飞。

是神的全知,让无人知晓的文明之始得以传世。

是神的全能,让牢不可破的数理之树兔死狐悲。

最大流

显然。\(O(n^2m)\)

费用流

显然。\(O(nmf)\)

上下界

loj 上有这三体的模板,懒得放链接了 = =

可行流

新建超源 \(SS\),超汇 \(TT\)

对于一条边 \(p\to q:[l,r]\),那就相当于保证要 \(p\) 点必须要流出 \(l\) 的流量,\(q\) 点必须要得到 \(l\) 的流量,然后跑 \(p\to q: r-l\) 的最大流。

于是我们就让 \(p\) 点连向 \(TT\),边权为 \(l\)\(SS\) 点连向 \(q\) 边权为 \(l\)。然后跑 Dinic。如果最大流不是所有的 \(l\) 之和,就说明有 \(p\) 没有贡献出 \(l\) 的流量,或者有 \(q\) 没有收到 \(l\) 的流量。(不然这些 \(l\) 的流量,会存在于 \(SS,TT\) 的连边中)。

实际操作中,我们给每个点设一个数组 hav 代表要导入/导出的流量总和。如果大于 0 就是要导入的流量,连边 \(SS\to i:|hav_i|\);小于 0 就是要导出的流量,连边 \(i\to TT:|hav_i|\)。然后判断是否 Dinic = \(\sum w_i\)

最大流

大体思路:先跑出一组可行流,再在残量网络上继续跑,看看能不能再流一些流量。

首先连边 \(T\to S:\inf\),这样就可以看成没有源点/汇点的情况了(因为源点是无限输出,汇点是无限输入)然后按照上文以 \(SS,TT\) 为源汇跑可行流。

这样我们得到了一组可行解。将 \(T\to S:\inf\) 断边。再以 \(S,T\) 为源汇继续跑一遍最大流即可。

最小流

参考最大流的做法,但是最后不是“再流一些流量”,而是要“再退回流量”。

于是把最后一步改为:以 \(S\) 为汇,以 \(T\) 为源(也就是 swap(S,T)),跑一遍最大流。答案就是可行流减去最大流。

最长反链

https://www.luogu.com.cn/problem/P4298 然而不会第二问。

首先根据胡克定律(Dilworth定理):最长反链 = 最小可重链覆盖。

然后我们对原图跑一遍传递闭包,就会最长反链 = 最小不可重链覆盖。(感性理解一下)

那么就可以这样理解:一开始每个点都是独立的链,每次可以把两条链首尾相接,最多可以操作几次。

发现,一个点在最终的链上,最多有一个前驱和一个后继,那么就把一个点拆成两个点 \(x_{in},x_{out}\)。对于一条边 \(x\to y\) 连边 \(x_{out}\to y_{in}\)(十分好理解!)。于是就有一个二分图,左边是全部的 out,右边是全部的 in。跑二分图最大匹配即可。假设最大匹配为 \(val\),答案就是 \(n-val\)

最大权闭合子图

题意

有一个有向图,每一个点都有一个权值(可以为正或负或0),选择一个权值和最大的子图,使得每个点的后继都在子图里面,这个子图就叫最大权闭合子图。

题解

https://blog.csdn.net/can919/article/details/77603353

二分图最大匹配 - 匈牙利算法

DFS

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
#define lon long long
using namespace std;
const int n7=101234,m7=1012345;
struct dino{int to,nxt;}e[m7];
int n1,n2,m,ecnt,fst[n7],ans,tim,vis[n7],mat[n7];

int rd(){
	int shu=0;bool fu=0;char ch=getchar();
	while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
	while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
	return fu?-shu:shu;
}

void edge(int p,int q){
	ecnt++;
	e[ecnt]=(dino){q,fst[p]};
	fst[p]=ecnt;
}

bool dfs(int o){
	vis[o]=tim;
	mar(o){
		if(mat[v])continue;
		mat[o]=v,mat[v]=o;
		return 1;
	}
	mar(o){
		int z=mat[v];
		if(vis[z]==tim)continue;
		mat[o]=v,mat[v]=o,mat[z]=0;
		if( dfs(z) )return 1;
		mat[o]=0,mat[v]=z,mat[z]=v;
	}
	return 0;
}

int main(){
	n1=rd(),n2=rd(),m=rd();
	rep(i,1,m){
		int p=rd(),q=n1+rd();
		edge(p,q),edge(q,p);
	}
	rep(i,1,n1){
		if(!mat[i])tim++,dfs(i);
	}
	rep(i,1,n1){
		if(mat[i])ans++;
	}
	printf("%d\n",ans);
	return 0;
}

BFS

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define mar(o) for(int E=fst[o];E;E=e[E].nxt)
#define v e[E].to
#define lon long long
using namespace std;
const int n7=101234,m7=1012345;
struct dino{int to,nxt;}e[m7];
int n1,n2,m,ecnt,fst[n7],ans,tim,vis[n7],mat[n7],pre[n7];
int head,tail,que[n7];

int rd(){
	int shu=0;bool fu=0;char ch=getchar();
	while( !isdigit(ch) ){if(ch=='-')fu=1;ch=getchar();}
	while( isdigit(ch) )shu=(shu<<1)+(shu<<3)+ch-'0',ch=getchar();
	return fu?-shu:shu;
}

void edge(int p,int q){
	ecnt++;
	e[ecnt]=(dino){q,fst[p]};
	fst[p]=ecnt;
}

void aug(int o){
	while(o){
		int tmp=mat[ pre[o] ];
		mat[o]=pre[o];
		mat[ pre[o] ]=o;
		o=tmp;
	}
}

void bfs(int o0){
	tim++;
	head=tail=1,que[head]=o0,vis[o0]=tim;
	while(head<=tail){
		int o=que[head];head++;
		mar(o){
			if(vis[v]==tim)continue;
			pre[v]=o;
			if(!mat[v]){aug(v);return;}//return 1;
			else{
				vis[v]=vis[ mat[v] ]=tim;
				tail++,que[tail]=mat[v];
			}
		}
	}
	//return 0
}

int main(){
	n1=rd(),n2=rd(),m=rd();
	rep(i,1,m){
		int p=rd(),q=n1+rd();
		edge(p,q),edge(q,p);
	}
	rep(i,1,n1){
		if(!mat[i])bfs(i);
	}
	rep(i,1,n1){
		if(mat[i])ans++;
	}	
	printf("%d\n",ans);
	return 0;
}

如果边带权怎么办?费用流!(似乎也有匈牙利做法,然而懒。)

(但是如果有负权边可能会影响,要删除此边)

一般图最大匹配 - 带花树

https://www.luogu.com.cn/blog/happydef-blog/yi-ban-tu-zui-da-pi-pei-xue-xi-bi-ji

\(O(nm)\)。(如果算上常数小的像 \(O(0)\) 的并查集,那就是 \(O(nm\log)\) 的)。

UPD:NT 才写带花树。直接用随机化稳过而且还好写:

  1. 重复十次二分图最大匹配;
  2. 每次枚举一个点的出边前,random_shuffle 一下边集。

gym102268A - Angle Beats

https://codeforc.es/gym/102268

题意

给你一个 \(n\times m\) 的网格图,每个点为 + * . 中的一个。一个 + 与四周任意两个 . 匹配,一个 * 可以与一个横方向和一个纵方向的 . 匹配(就是匹配形成一个 L 字形)。

求最大匹配 \(n,m\le 100\)

题解

对于 * 拆成两个点,四周的一个 . 与这两个点连边。对于 + 也拆成两个点,横向的 . 与第一个点连边,纵向的点与另一个点连边。

然后拆成的两个点也要互相连边,这是为了如果出现

+ . 
. +

的情况,可能会两个加号分别匹配两个点。所以连边就会使得不匹配有 1 的贡献,那么遇到上述的情况,就会优先不连边。

于是这样就保证了正确性,直接跑 FakeBloomingTree 即可。

UPD:不能跑 FakeBloomingTree 会被卡,要用真正的 BloomingTree!

posted @ 2022-03-22 18:50  BlankAo  阅读(155)  评论(0编辑  收藏  举报