二分图匹配与带权匹配

二分图最大匹配,二分图带权匹配

打第五场牛客多校的时候发现KM的板子复杂度假了,特来补上,顺带复习一下

二分图最大匹配

匈牙利算法

交替路:从一个未匹配点出发,依次经过非匹配边,匹配边,非匹配边\(\cdots\),形成的路径叫交替路。

增广路:途径交替路的起点之外的其他未匹配点的交替路叫做增广路。其一个重要性质是:非匹配边比匹配边多一条,因此交换增广路中的匹配边和非匹配边可以使匹配数+1。

增广路定理:当找不到增广路时,达到最大匹配。

匈牙利算法求最大匹配就是不断寻找增广路,每次找到增广路可以使匹配数+1,时间复杂度\(O(VE)\),空间复杂度\(O(E)\)

UOJ #78 二分图最大匹配:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1007;
vector<int> adj[maxn];
bool vis[maxn]; //一次dfs过程中,右半边集合中的顶点是否已访问。
int ans[maxn], res[maxn];  //右半边集合中的点匹配的左半边点的编号。
int nl, nr, m;
bool dfs(int u) {
    for (int i = 0; i < adj[u].size(); i++) {
        int v = adj[u][i];
        if (!vis[v]) {  //如果右半边的点v还未被访问
            vis[v] = true;
            if (ans[v] == -1 || dfs(ans[v])) {
                //如果v没有匹配点,或是v的匹配点能找到一条到未匹配点的增广路
                ans[v] = u;
                res[u] = v;
                return true;
            }
        }
    }
    return false;
}
int maxmatch() {
    int res = 0;
    memset(ans, -1, sizeof(ans));
    for (int i = 1; i <= nl; i++) {
        memset(vis, 0, sizeof(vis));
        res += dfs(i);
    }
    return res;
}

int main() {
    cin >> nl >> nr >> m;
    for (int i = 1; i <= m; i++) {
        int v, u;
        scanf("%d%d",&v,&u);
        adj[v].push_back(u);
    }
    printf("%d\n", maxmatch());
    for (int i = 1; i <= nl; i++) {
        printf("%d ", res[i]);
    }
    return 0;
}

HK算法

UOJ上的榜一写的Hopcroft-Krap算法,9msAC,,,时间复杂度\(O(\sqrt{V}E)\),实际速度很快

hopcroft-karp算法二分图大讲堂——彻底搞定最大匹配数(最小覆盖数)、最大独立数、最小路径覆盖、带权最优匹配 - One thing I know,that is I know nothing.(Socrates Greek) - ITeye博客学习一波

HK算法的流程如下:

用bfs对图进行分层,左部点中的所有未匹配点组成第一层,把经过的匹配点全部加入队列,直到所有未访问的匹配点都加入队列。(bfs保证了不相交且最短)然后使用dfs遍历bfs找出的所有增广路(按层次走),每条增广路可以使匹配数+1.不断重复bfs和dfs,直到找出了增广路。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1007, inf = 0x3f3f3f3f;
vector<int> adj[maxn];
int ansx[maxn], ansy[maxn], dx[maxn], dy[maxn], dis, nl, nr, m;
//ansx,ansy储存的分别是左边点和右边点对应的匹配点,dx和dy是bfs分出的层次,dis是当前bfs的增广路的长度
bool vis[maxn];
bool bfs() {
    queue<int> q;
    dis = inf;
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    //先找出左半边集合中的未匹配点作为bfs的源点
    for (int i = 1; i <= nl; i++) {
        if (ansx[i] == -1) {
            q.push(i);
            dx[i] = 0;
        }
    }
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        if (dx[u] > dis) break; 
        for (int i = 0; i < adj[u].size(); i++) {
            int v = adj[u][i];
            if (dy[v] == -1) {
                dy[v] = dx[u] + 1;
                if (ansy[v] == -1) dis = dy[v]; //每一轮bfs  增广路的长度+1
                else {
                    dx[ansy[v]] = dy[v] + 1;
                    q.push(ansy[v]);
                }
            }
        }
    }
    return dis != inf;
}
bool dfs(int u) {
    for (int i = 0; i < adj[u].size(); i++) {
        int v = adj[u][i];
        if (!vis[v] && dy[v] == dx[u] + 1) {
            vis[v] = 1;
            //if (ansy[v] != -1 || dy[v] == dis) continue;
            if (ansy[v] == -1 || (dy[v] != dis && dfs(ansy[v]))) {
                ansy[v] = u;
                ansx[u] = v;
                return true;
            }
        }
    }
    return false;
}
int maxmatch() {
    int res = 0;
    memset(ansx, -1, sizeof(ansx));
    memset(ansy, -1, sizeof(ansy));
    while (bfs()) {
        memset(vis, 0, sizeof(vis));
        for (int i = 1; i <= nl; i++) {
            if (ansx[i] == -1) 
                res += dfs(i);
        }
    }
    return res;
}
int main() {
    scanf("%d%d%d", &nl, &nr, &m);
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        adj[u].push_back(v);
    }
    printf("%d\n", maxmatch());
    for (int i = 1; i <= nl; i++) {
        printf("%d ", ansx[i] == -1 ? 0 : ansx[i]);
    }
}

最大流

略,复杂度\(O(n^{0.5}m)\)

二分图带权匹配

费用流要注意spfa会被卡(牛客多校5 J),dijkstra费用流没去试,m=n^2时应该也好不到哪里去

正确写法的KM算法是严格\(O(n^3)\)

AcWing 375. 蚂蚁 - O(n^3) DFS版 KM算法 - AcWing

lyd给的真\(O(n^3)\)的dfs版KM

KM(匈牙利)算法:

P6577 【模板】二分图最大权完美匹配 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

二分图最大权匹配 - OI Wiki (oi-wiki.org)

可以在\(O(n^3)\)的时间内求出二分图的最大权完美匹配
抄的lyd的板子,比榜一慢了五倍
UOJ#80

#include<bits/stdc++.h>
using namespace std;
const int maxn = 407;
#define ll long long
const ll inf = 9999999999ll;
int nl, nr, m, n;
int ansx[maxn], ansy[maxn], last[maxn];
ll slack[maxn], lx[maxn], ly[maxn], w[maxn][maxn];
bool visx[maxn], visy[maxn];//是否在交错树中
bool dfs(int u, int fa) {
	visx[u] = true;
	for (int v = 1; v <= n; v++) {
		if (!visy[v]) {	//每次顶标的修改值为min{slack[v],v是不在交错树中的右部点}
			if (lx[u] + ly[v] == w[u][v]) {
				visy[v] = true;
				last[v] = fa;	//右部点在交错树中的上一个右部点 倒推即可得到交错路
				if (!ansy[v] || dfs(ansy[v], v)) {
					ansy[v] = u;
//					ansx[u] = v;
					return true;
				}
			} else {
				if (slack[v] > lx[u] + ly[v] - w[u][v]) {
					slack[v] = lx[u] + ly[v] - w[u][v];
					last[v] = fa;
				}
			}
		}
	}
	return false;
}
ll KM(){
	for (int i = 1; i <= n; i++) {
		lx[i] = -inf;
		ly[i] = 0;
		for (int j = 1; j <= n; j++) 
			lx[i] = max(lx[i], w[i][j]);
	}
	for (int i = 1; i <= n; i++) {
		memset(visx, 0, sizeof(visx));
		memset(visy, 0, sizeof(visy));
		for (int j = 1; j <= n; j++) {
			slack[j] = inf;
		}
		int st = 0;
		ansy[0] = i;
		//假设一开始有一条从右边的0到左边的i的匹配
		while (ansy[st]) {
			ll delta = inf;
			if (dfs(ansy[st], st)) 
				break;	//找到
			for (int j = 1; j <= n; j++) {
				if (!visy[j] && delta > slack[j]) {
					delta = slack[j];
					st = j;
					//下一次从最小的边开始dfs 保证全图累计只遍历一次
				}
			}
			for (int j = 1; j <= n; j++) {	//修改顶标
				if (visx[j]) lx[j] -= delta;
				if (visy[j]) ly[j] += delta;
				else slack[j] -= delta;
			}
			visy[st] = true;
		}
		while (st) {	//更新增广路
			//ansx[ansy[st]] = 0;
			ansy[st] = ansy[last[st]];
			//ansx[ansy[st]] = st;
			st = last[st];
		}
	}
	ll res = 0;
	for (int i = 1; i <= nr; i++) 
		ansx[ansy[i]] = i;
	for (int i = 1; i <= nl; i++) {
		res += w[i][ansx[i]];
	}
	return res;
}
int rd() {
	int s = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') { if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9') {s = s * 10 + c - '0'; c = getchar();}
	return s * f;
}
//如果求最大权完美匹配,就把不存在的边设为-inf
int main(){
	nl = rd(); nr = rd(); m = rd();
	n = max(nl, nr);
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= n; j++)
			w[i][j] = 0;//本题求的不是最大权完美匹配
	for (int i = 1; i <= m; i++) {
		int u = rd(), v = rd();
		w[u][v] = rd();
	//	w[u][v] = max(0ll, w[u][v]);
	}	
	cout << KM() << "\n";
	for (int i = 1; i <= nl; i++) {
		printf("%d ", w[i][ansx[i]] <= 0 ? 0 : ansx[i]);
	}
	return 0;
}

2021牛客多校第五场J

#include<bits/stdc++.h>
using namespace std;
const int maxn = 307;
#define ll long long
const ll inf = 99999999999ll;
struct _ {
    ll x, y, z, v;
}a[maxn];
ll ans, lx[maxn], ly[maxn], n, slack[maxn], ansy[maxn], ansx[maxn], last[maxn], w[maxn][maxn];
bool visx[maxn], visy[maxn];
bool dfs(int u, int fa) {
    visx[u] = true;
    for (int v = 1; v <= n; v++) {
        if (!visy[v]) {
            if (lx[u] + ly[v] == w[u][v]) {
                visy[v] = true;
                last[v] = fa;
                if (!ansy[v] || dfs(ansy[v], v)) {
                    ansy[v] = u;
                    return true;
                }
            } else if (slack[v] > lx[u] + ly[v] - w[u][v]) {
                slack[v] = lx[u] + ly[v] - w[u][v];
                last[v] = fa;
            }
        }
    }
    return false;
}
 
ll km() {
    for (int i = 1; i <= n; i++) {
        lx[i] = -inf;
        ly[i] = 0;
        for (int j = 1; j <= n; j++) {
            lx[i] = max(lx[i], w[i][j]);
        }
    }
    for (int i = 1; i <= n; i++) {
        memset(visx, 0, sizeof(visx));
        memset(visy, 0, sizeof(visy));
        for (int j = 1; j <= n; j++) {
            slack[j] = inf;
        }
        int st = 0;
        ansy[0] = i;
        while (ansy[st]) {
            ll delta = inf;
            if (dfs(ansy[st], st))
                break;
            for (int j = 1; j <= n; j++) {
                if (!visy[j] && delta > slack[j]) {
                    delta = slack[j];
                    st = j;
                }
            }
            for (int j = 1; j <= n; j++) {
                if (visx[j]) lx[j] -= delta;
                if (visy[j]) ly[j] += delta;
                else slack[j] -= delta;
            }
            visy[st] = true;
        }
        while (st) {
            ansy[st] = ansy[last[st]];
            st = last[st];
        }
    }
    ll res = 0;
    for (int i = 1; i <= n; i++) {
        ansx[ansy[i]] = i;
    }
    for (int i = 1; i <= n; i++)
        res += w[i][ansx[i]];
    return res;
}
int rd(){
    int s = 0, f = 1;
    char c = getchar();
    while (c < '0'||c > '9'){if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') { s = s * 10 + c - '0'; c = getchar();}
    return s * f;
}
int main(){
    n = rd();
    for (int i = 1; i <= n; i++) {
        a[i].x = rd();
        a[i].y = rd();
        a[i].z = rd();
        a[i].v = rd();
        ans += a[i].x * a[i].x + a[i].y * a[i].y;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            w[i][j] = -(a[i].v * (j-1) + a[i].z) * (a[i].v *(j-1) + a[i].z);
        }
    }
    cout << ans - km();
}
posted @ 2021-08-05 05:35  yjmstr  阅读(241)  评论(0编辑  收藏  举报