二分图学习笔记(二)

主要是二分图的多重匹配与带权匹配。

多重匹配

与普通二分图最大匹配不同的是,每个节点可以匹配多条边。左部第\(i\)个节点可以匹配\(kl_i\)条,右部第\(j\)个节点可以匹配\(kr_j\)条。问最多匹配几条。
多重匹配的最佳做法是网络流。二分图也可以处理,但效率低下。不过在所有\(kl_i\)=1或所有\(kr_j\)=1时二分图也是一种好方法。不妨设左部节点多重而所有\(kr_j\)=1,那么可以用匈牙利算法对每个左部节点求\(kl_i\)遍dfs。另一种方法是拆点处理。
例:导弹拦截塔Acwing374
答案具有单调性,于是可以二分,转化为判断mid分钟内可否击退所有入侵者。
将入侵者看做二分图的左部,拦截塔看做右部。那么在左部与右部之间进行匹配。显然,左部每点只能匹配一条边,右部可以匹配多条。
将每个拦截塔拆成m个,其中第i个表示“此拦截塔发射的第i枚导弹”。于是问题转化为了二分图最大匹配。
左部第x点与右部第i点的第j个分点有边当且仅当\(t1×j+t2×(j-1)+\frac{dis(x,i)}{v}≤mid\)

完备匹配

若二分图的最大匹配中包含所有点,则为完备匹配。

带权匹配

二分图中边有权值。要求在最大匹配的前提下,使匹配边的总权值最大。
方法有两种:费用流与KM算法。费用流更佳。

KM算法

优点:程序实现简单,稠密图上效率更高。
缺点:稀疏图效率低,有局限性,只能处理最大匹配为完备匹配的情况。

设二分图左右两部节点数均为N。
引入一些概念:
顶标:顶点标记值。左部第i个节点顶标为Ai,右部第j个节点顶标为Bj。连接任意左i与右j的边w必须满足\(w(i,j)≤A_i+B_j\)。于是,我们可以简单地理解顶标为“希望匹配的最大的边权”。
交错树:如果第i个节点匹配失败,那么它dfs的过程构成一棵树。称为交错树。
相等子图:二分图的一张子图,满足:包含所有节点,以及所有\(w(i,j)=A_i+B_j\)的边。不难发现,若二分图存在完备匹配(正好是KM算法的前提条件),则二分图的带权最大匹配一定是其相等子图的完备匹配。这点很重要,它使原问题转化为求解相等子图的完备匹配。

KM算法的基本思想是,先在满足\(∀i,j,A_i+B_j≥w(i,j)\)的前提下,给每个节点随意赋值一个顶标,然后采取适当的策略不断扩大相等子图的规模,直到相等子图存在完全匹配。

在了解了KM算法的实现后再来看这句话,真是概括到了精髓。
先对顶标进行构造,例如,使\(A_i=\max_{1≤j≤N}{w(i,j)}\),Bj=0。然后依次考虑每个左部点i。若i成功匹配,那么不作处理;否则在满足相等子图的前提下,调整顶标,更换1~i-1的匹配对象(此处“调整顶标”操作有两个要点:一是仍要满足Ai+Bj≥w(i,j),二是使i点向着能够完成匹配的方向发展),使i成功匹配。
记当前未匹配的点为x,点x的交错树为T。具体来说,将交错树中所有左部点顶标减小一个整数值Δ,所有右部点增加Δ。
T的叶子结点一定在左部,设其中一点为i,右部一点为j。由于i是叶子结点,即不与i同在相等子图中,于是Ai+Bj>w(i,j)。顶标改变后,Ai减小,Bj不变。于是发现:j有可能加入到相等子图中了!
再看同在T中的两点i,j(i在左部,i,j相连)。Ai减小Δ,Bj增大Δ,于是此两点还在相等子图中。综上,相等子图的规模扩大了。
KM算法不断重复以上过程,一定可以找到相等子图的完备匹配,即找到最优解。
再分析Δ的选择。对于i∈T,j∉T。要使\(A_i-Δ+B_j≥w(i,j)\),则\(Δ≤A_i+B_j-w(i,j)\)。对于所有右部点,取最小值即可。

const int N = 100 + 5;
int n, w[N][N], match[N];
int la[N], lb[N], upd[N];
bool va[N], vb[N];
bool dfs(int x) {
	va[x] = 1;
	for (int y = 1; y <= n; y++) {
    	if (vb[y]) continue;
    	if (la[x] + lb[y] == d[x][y]) {
        	vb[y] = 1;
        	if (!match[y] || dfs(match[y])) {
            	match[y] = x; return 1;
            }
        }
    	else upd[y] = min(upd[y], la[x] + lb[y] - d[x][y]);
    }
	return 0;
}
int KM() {
	for (int i = 1; i <= n; i++) {
    	la[i] = -INF; lb[i] = 0;
    	for (int j = 1; j <= n; j++)
        	la[i] = max(la[i], w[i][j]);
    }
	for (int i = 1; i <= n; i++)
    	while (1) {
        	memset(va, 0, sizeof (va));
        	memset(vb, 0, sizeof (vb));
        	memset(upd, INF, sizeof (upd));
        	if (dfs(i)) break;
        	int delta = INF;
        	for (int j = 1; j <= n; j++)
            	if (!vb[j]) delta = min(delta, upd[j]);
        	for (int j = 1; j <= n; j++) {
            	if (va[j]) la[j] -= delta;
            	if (vb[j]) lb[j] += delta;
            }
        }
}

例:AntsAcwing375
主要是结论:所有线段不相交⇔所有线段的长度之和最小。
若两条线段\(i_1j_1\)\(i_2j_2\)相交,那么连接\(i_1j_2\)\(i_2j_1\)。这两条线段不相交,且长度更短(三角形两边之和大于第三边)。可以推广到整张图。
还有一点要注意,题中距离为实数,故需要考虑精度。因为长度的加减可能导致微小的偏差,使得Ai+Bj≠w(i,j)。可以设置eps,允许10-3以内的偏差。

posted @ 2022-03-04 17:09  realFish  阅读(122)  评论(0)    收藏  举报