1️⃣增廣路徑
1.概念:
若路徑P是圖G中一條連通兩個頂點,這兩個頂點與M中無匹配關係的路徑,且屬於M的邊和不屬於M的邊在P上交替出現,則稱P爲相對於M的一條增廣路徑。
2.性質
圖中的增廣路徑P爲12345
- 根據概念可知,處於P兩端的邊一定不屬於M,且P長度肯定是奇數的
屬於M的邊有2和4,匹配數量爲2,其補圖有邊1,3,5,匹配數量爲3
- 增廣路徑取反後必然能得到一個更大的匹配
- M爲最大匹配當且僅當增廣路徑P不存在
這就提供了一個求二分圖最大匹配的一個思路:
如果增廣路徑的長度不斷變大,就相當於M的匹配也在變大!
2️⃣匈牙利算法解決二分圖最大匹配
(注意:點不能重複使用,故需要用一個數組進行標記)
一開始,M中沒有匹配關係,隨意取一條
然後對於,也可以取,M變成下圖的匹配關係
但是當想要和匹配的時候發現已經被佔用了
就把的原主暴揍一頓趕走了
很不爽就去找"第二歸宿",結果還是佔用着
就把揍了一頓,於是只能去找
這是一個遞歸揍人的過程,揍完之後的匹配圖如下:
路徑:
在圖二中對照發現正好是一條增廣路徑
取反之後得到圖三,此時的匹配狀態由於構造出了增廣路徑,故匹配數量變大,由2變成了3
匈牙利算法的算法核心就是通過構造增廣路徑來擴大匹配。
至於這個構造的過程就是遞歸揍人的過程,反正我不懂是如何實現的,不深究
3️⃣例題
①51Nod 2006
直接對兩部分進行匹配
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#include<map>
#define ll long long
#define pb push_back
#define rep(x,a,b) for (int x=a;x<=b;x++)
#define repp(x,a,b) for (int x=a;x<b;x++)
#define W(x) printf("%d\n",x)
#define mem(a,x) memset(a,x,sizeof a)
using namespace std;
const int maxn=1e3+7;
int mapp[maxn][maxn];
int linker[maxn];
bool vis[maxn];
int ans,n,m,a,b;
bool dfs(int u)
{
for (int i=m+1;i<=n;i++)
{
if (mapp[u][i]&&!vis[i])
{
vis[i]=true;
if (linker[i]==-1||dfs(linker[i]))
{
linker[i]=u;
return true;
}
}
}
return false;
}
void init()
{
mem(linker,-1);
ans=0;
}
int count_()
{
for (int i=1;i<=m;i++)
{
mem(vis,false);
if (dfs(i))ans++;
}
return ans;
}
int main()
{
cin>>m>>n;
init();
while(scanf("%d%d",&a,&b)&&!(a==-1&&b==-1))
{
mapp[a][b]=1;
}
int x=count_();
if (x==0)cout<<"No Solution!"<<endl;
else cout<<x<<endl;
return 0;
}
②洛谷P3386(模板題)
代碼略
https://www.luogu.com.cn/problemnew/show/P3386
③PKUOJ1469(模板題)
代碼略
http://poj.org/problem?id=1469
④洛谷P1640
https://www.luogu.com.cn/problem/P1640
屬性值當作一個點,武器當作一個點,一個武器對應連兩個屬性值的邊
兩部分進行二分匹配
不太一樣的是:選擇第i+1屬性值的前提是選擇第i屬性值,那麼在匈牙利算法時,如果dfs(i)失敗時,即無法將第i個屬性值納入增廣路徑,就break退出即可
本題由於數據範圍,不能使用鄰接矩陣存儲,改寫鄰接矩陣
const int maxn=2e6+7;
int linker[maxn],n,a,b;
bool vis[maxn];
struct Edge
{
int to,next;
}edge[maxn];
int head[maxn],tot;
void init()
{
tot=0;
mem(head,-1);
mem(linker,-1);
}
void add(int u,int v)
{
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
bool dfs(int u)
{
for (int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if (!vis[v])
{
vis[v]=true;
if (linker[v]==-1||dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
}
return false;
}
int count_()
{
int ans=0;
for (int i=1;i<=10000;i++)
{
mem(vis,false);
if (dfs(i))ans++;
else break;
}
return ans;
}
int main()
{
cin>>n;
init();
rep(i,1,n)
{
scanf("%d%d",&a,&b);
add(a,i);
add(b,i);
}
W(count_());
return 0;
}
⑤洛谷P1129
https://www.luogu.com.cn/problem/P1129
將矩陣的行[1-n]和矩陣的列[1-n]作爲左右二部分
可以證明的是當最大匹配達到n的時候
通過幾次行變換就可以實現對角線全是1
const int maxn=2e2+7;
bool vis[maxn];
int mapp[maxn][maxn];
int linker[maxn],n,x,T;
bool dfs(int u)
{
for (int i=1;i<=n;i++)
{
if (mapp[u][i]&&!vis[i])
{
vis[i]=true;
if (linker[i]==-1||dfs(linker[i]))
{
linker[i]=u;
return true;
}
}
}
return false;
}
int count_()
{
int ans=0;
for (int i=1;i<=n;i++)
{
mem(vis,false);
if (dfs(i))ans++;
}
return ans;
}
int main()
{
scanf("%d",&T);
while(T--)
{
mem(linker,-1);
mem(mapp,0);
scanf("%d",&n);
rep(i,1,n)
{
rep(j,1,n)
{
scanf("%d",&x);
if (x==1)mapp[i][j]=1;
}
}
if (count_()==n)cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
⑥洛谷1963
https://www.luogu.com.cn/problem/P1963
左邊爲[0~n-1]的排列,右邊爲每一個數字衍生出的兩個可能性,用最大匹配解即可
如果是從正向建立增廣路徑,那麼會後面的會不斷的覆蓋前面的最優解,那麼正確的做法就是從後到前,這時候記錄的也就不是linker,而是作爲反函數的inv
還有一個坑點就是這兩個衍生數字的大小問題,應該是優先選擇小的,所以小的在遍歷過程中應該處於前面點的位置。
const int maxn=2e6+7;
int tot,linker[maxn],n,a[maxn],inv[maxn];
bool vis[maxn];
int mapp[maxn][2];
bool dfs(int u)
{
for (int i=0;i<2;i++)
{
int v=mapp[u][i];
if (!vis[v])
{
vis[v]=true;
if (linker[v]==-1||dfs(linker[v]))
{
linker[v]=u;
inv[u]=v;
return true;
}
}
}
return false;
}
int count_()
{
int ans=0;
for (int i=n-1;i>=0;i--)
{
mem(vis,false);
if (dfs(i))ans++;
}
return ans;
}
int main()
{
scanf("%d",&n);
mem(linker,-1);
rep(i,0,n-1)
{
scanf("%d",&a[i]);
int p=(i+a[i])%n;
int q=(i-a[i]+n)%n;
if (p>q)swap(p,q);
mapp[i][0]=p;mapp[i][1]=q;
}
int x=count_();
if (x<n)cout<<"No Answer"<<endl;
else
{
rep(i,0,n-1){cout<<inv[i];if (i!=n-1)cout<<" ";}cout<<endl;
}
return 0;
}
⑦洛谷P3231
https://blog.csdn.net/w_udixixi/article/details/103982191
⑧HDU1281
棋盤問題直接最大匹配處理,但是“重要的點”不能一眼看出,就先把它去掉,再算一次最大匹配,如果小於原值,就說明是重要的點
算法過於暴力,時間夠
const int maxn=2e2+7;
const int INF=1e9;
const ll INFF=1e18;
int mapp[maxn][maxn],x[maxn],y[maxn];
bool vis[maxn];
int linker[maxn];
int n,m,k,cnt=1;
bool dfs(int u)
{
for (int i=1;i<=m;i++)
{
if (mapp[u][i]&&!vis[i])
{
vis[i]=true;
if (linker[i]==-1||dfs(linker[i]))
{
linker[i]=u;
return true;
}
}
}
return false;
}
int count_()
{
int ans=0;
for (int i=1;i<=n;i++)
{
mem(vis,false);
if (dfs(i))ans++;
}
return ans;
}
int main()
{
while(~scanf("%d%d%d",&n,&m,&k))
{
mem(linker,-1);
mem(mapp,0);
rep(i,1,k)
{
scanf("%d%d",&x[i],&y[i]);
mapp[x[i]][y[i]]=1;
}
int X=count_(),num=0;
for (int i=1;i<=k;i++)
{
mem(linker,-1);
for (int j=1;j<=k;j++)
{
if (j==i)mapp[x[i]][y[i]]=0;
}
int Y=count_();
if (Y<X)num++;
mapp[x[i]][y[i]]=1;
}
printf("Board %d have %d important blanks for %d chessmen.\n",cnt++,num,X);
}
}
4️⃣Hopcroft-Karp算法
複雜度:
最大匹配建圖是核心,那麼這個算法就不研究了。
一般一提到最大匹配就會想到匈牙利算法,但是這種的算法複雜度確實是要小一點的,沒遇到過具體的題目所以不知所用
下面放一道模板題:除了vector存圖其餘全是板子
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<queue>
#include<map>
#define ll long long
#define pb push_back
#define rep(x,a,b) for (int x=a;x<=b;x++)
#define repp(x,a,b) for (int x=a;x<b;x++)
#define W(x) printf("%d\n",x)
#define mem(a,x) memset(a,x,sizeof a)
using namespace std;
const int maxn=2e3+7;
const int INF=1e9;
vector<int> G[maxn];
int Mx[maxn],My[maxn];
int dx[maxn],dy[maxn];
int dis,p,n;
bool vis[maxn];
bool bfs()
{
queue<int> Q;
dis=INF;
mem(dx,-1);mem(dy,-1);
for (int i=1;i<=p;i++)
{
if (Mx[i]==-1)
{
Q.push(i);
dx[i]=0;
}
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
if (dx[u]>dis)break;
repp(i,0,G[u].size())
{
int v=G[u][i];
if (dy[v]==-1)
{
dy[v]=dx[u]+1;
if (My[v]==-1)dis=dy[v];
else
{
dx[My[v]]=dy[v]+1;
Q.push(My[v]);
}
}
}
}
return dis!=INF;
}
bool dfs(int u)
{
repp(i,0,G[u].size())
{
int v=G[u][i];
if (!vis[v]&&dy[v]==dx[u]+1)
{
vis[v]=true;
if (My[v]!=-1&&dy[v]==dis)continue;
if (My[v]==-1||dfs(My[v]))
{
My[v]=u;
Mx[u]=v;
return true;
}
}
}
return false;
}
int count_()
{
int res=0;
mem(Mx,-1);
mem(My,-1);
while(bfs())
{
mem(vis,false);
for (int i=1;i<=p;i++)if (Mx[i]==-1&&dfs(i))res++;
}
return res;
}
int main()
{
int t,a,b;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&p,&n);
rep(i,1,p)G[i].clear();
rep(i,1,p)
{
scanf("%d",&a);
repp(j,0,a)
{
scanf("%d",&b);
G[i].pb(b);//vector存圖-
//這是一道模板題,最基本的建圖
}
}
int x=count_();
if (x==p)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}