用於二分圖匹配問題
二分圖:有兩個集合,集合內元素互不相連接,兩個集合間元素和元素間可互相連接,如下圖
定義:找到最多合法匹配元素使的總數最多
原則:
// p[i]代表與此元素匹配的元素,初始化為0
bool match(int now){
for(int i=1;i<=N;i++){
if(Map[now][i]==1){ //如果此2元素有連接才能走
if(!p[i]){
//如果此元素還未被配對
p[i]=now;
//直接與此元素配對
return true;
}else{ //否則此元素已被配對
if(match(p[i])){
//檢查此已配對元素是否可與其他元素配對
p[i]=now;
//如果可以就把此元素改成當前元素now
return true;
}
}
}
}
return false;
}
兩者可合併簡化:
bool match(int now){
for(int i=1;i<=N;i++){
if(Map[now][i]){
if(!p[i] || match(p[i])){
p[i]=now;
return true
}
}
}
return false;
}
但由於遞迴的關係可能會回到起始點,就會從起始點再一次遞迴,最後會TLE,所以要判斷此點是否visited
//p[i]代表右邊的i和左邊的p[i]節點匹配
//Map[x][y]代表左邊的x節點和右邊的y節點有連通
bool match(int now){
for(int i=1;i<=N;i++){
if(Map[now][i] && !visit[i]){ //判斷該點有沒有被訪問過
visit[i]=true; //代表該點已走過,不用在判斷能否匹配
if(!p[i] || match(p[i])){
p[i]=now;
return true
}
}
}
return false;
}
圖的版本:
bool match(int now){
for(auto i:Map[now]){
if(!vis[i]){
vis[i]=true;
if(p[i]==0 || match(p[i])){
p[i]=now;
return true;
}
}
}
return false;
}
定義:找到一些最少的點,使的二分圖所有的邊都至少有一個端點在這些點之中,也就是說,刪掉包含這些點的邊,可以刪掉二分圖所有的邊
題目:zerojudge C455
#include <bits/stdc++.h>
using namespace std;
#define MAXN 100005
vector<int> graph[MAXN];
int match[MAXN];
bitset<MAXN> vis;
bool Try(int now) {
for (auto i : graph[now]) {
if (!vis[i]) {
vis[i] = true;
if (!match[i] || Try(match[i])) {
match[i] = now;
return true;
}
}
}
return false;
}
int main() {
cin.sync_with_stdio(0), cin.tie(0);
int t, n, m;
cin >> t;
while (t--) {
int k, x, y;
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) graph[i].clear();
for (int i = 1; i <= m; i++) match[i] = 0;
for (int i = 0; i < k; i++) {
cin >> x >> y;
graph[x].push_back(y);
}
int matchCnt = 0;
for (int i = 1; i <= n; i++) {
vis.reset();
if (Try(i)) matchCnt++;
}
cout << matchCnt << '\n';
}
return 0;
}
上述匈牙利演算法是用來求最大匹配數,KM演算法則是求最大權重完美匹配數,也就是說,每個元素都要有匹配,求最大權重為何
總共會需要以下陣列
int love[MAXN][MAXN]; //兩點元素之間的權重(好感度)
int ex_girl[MAXN]; //girl的頂標
int ex_boy[MAXN]; //boy的頂標
int vis_girl[MAXN]; //有無拜訪過這個girl
int vis_boy[MAXN]; //有無拜訪過這個boy
int match[MAXN]; //這個boy配對到的girl
int N,minz; //minz表最小機會成本
void init(){
for(int i=0;i<N;i++){
ex_boy[i]=0;
match[i]=-1;
ex_girl[i]=love[i][0];
for(int j=1;j<N;j++){
ex_girl[i]=max(ex_girl[i],love[i][j]);
}
}
}
bool dfs(int girl){
vis_girl[girl]=true;
for(int boy=0;boy<N;boy++){
if(!vis_boy[boy] && ex_girl[girl]+ex_boy[boy]==love[girl][boy]){
//頂標和=權重才能更新
vis_boy[boy]=true;
if(match[boy]==-1 || dfs(match[boy])){
match[boy]=girl;
return true;
}
}else if(!vis_boy[boy]){
//對於參與配對的girl與未參與配對的boy才要求最小機會成本
minz=min(minz,ex_girl[girl]+ex_boy[boy]-love[girl][boy]);
}
}
return false;
}
void update(){
for(int i=0;i<N;i++){
if(vis_girl[i]) ex_girl[i]-=minz;
if(vis_boy[i]) ex_boy[i]+=minz;
}
}
完整Code
1042 . E.老問題
#include <bits/stdc++.h>
#define MAXN 105
using namespace std;
int love[MAXN][MAXN];
int ex_girl[MAXN];
int ex_boy[MAXN];
int vis_girl[MAXN];
int vis_boy[MAXN];
int match[MAXN];
int N,minz;
bool dfs(int girl){
vis_girl[girl]=true;
for(int boy=0;boy<N;boy++){
if(!vis_boy[boy] && ex_girl[girl]+ex_boy[boy]==love[girl][boy]){
vis_boy[boy]=true;
if(match[boy]==-1 || dfs(match[boy])){
match[boy]=girl;
return true;
}
}else if(!vis_boy[boy]){
minz=min(minz,ex_girl[girl]+ex_boy[boy]-love[girl][boy]);
}
}
return false;
}
void init(){
for(int i=0;i<N;i++){
ex_boy[i]=0;
match[i]=-1;
ex_girl[i]=love[i][0];
for(int j=1;j<N;j++){
ex_girl[i]=max(ex_girl[i],love[i][j]);
}
}
}
void update(){
for(int i=0;i<N;i++){
if(vis_girl[i]) ex_girl[i]-=minz;
if(vis_boy[i]) ex_boy[i]+=minz;
}
}
int KM(){
init();
//KM
for(int j=0;j<N;j++){ //為每個girl都配對一個boy
while(1){
//init
memset(vis_girl,0,sizeof(ex_girl));
memset(vis_boy,0,sizeof(ex_boy));
minz=1e9;
if(!dfs(j)) update(); //如果配對失敗,則更新頂標
else break; //直到配對成功
}
}
//get ans
int ans=0;
for(int i=0;i<N;i++){
ans+=love[match[i]][i];
//把match[i]這個女生對應到的男生i的好感度加到ans
}
return ans;
}
int main(){
cin.sync_with_stdio(0),cin.tie(0);
while(cin >> N && N){
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){
cin >> love[i][j];
love[i][j]=max(love[i][j],0);
}
}
cout << KM() << '\n';
}
return 0;
}