二分图匹配(一)
文章目錄
- 什么是二分圖:
- 例題:
- NC111768 CF741C
- 題目描述:
- 題解:
- 代碼:
- 二分圖最大匹配
- 匈牙利算法
- 算法思想:
- 代碼:
- K?nig定理
- 二分圖最優匹配
- KM(Kuhn-Munkres)算法
- 算法思路:
- 具體操作
- 代碼:
- 復雜度
- 代碼:
什么是二分圖:
二分圖充分必要條件條件:
至少有兩個頂點且沒有奇環
二分圖判斷:
通過黑白染色
例題:
NC111768 CF741C
題目描述:
有n對情侶(2n個人)圍成一圈坐在桌子邊上,每個人占據一個位子,要求情侶不能吃同一
種食物,并且桌子上相鄰的三個人的食物必須有兩個人是不同的,只有兩種食物(1或者是2),問一種可行分配方式。
題解:
如果我們能把不能吃同一種食物的人連邊,問題就變成二分圖黑白染色
? 所以情侶關系等價于兩者之間連一條邊
? “每連續的三個人不能都一樣”怎么辦?
? 讓第2i個人和第2i+1個人不能吃一樣的食物即可(即1連2,3連4,5連6以此類推)
? 這樣肯定是個二分圖——2i和2i-1分別連了他兩的情侶,情侶又分別連他兩的一個鄰居……
每次都是給這個可能存在的環加兩個點,所以有環就一定不是奇環
構造方法如下:首先情侶連邊,然后在原圖的相鄰點對上,隔一對連一條邊
代碼:
二分圖最大匹配
促成更多的點配對
匈牙利算法
有一場宴會,男孩和女孩組成舞伴,并且他們必須互相喜歡才能成為舞伴,一個男孩可能喜歡0個或多個女孩,一個女孩也可能喜歡0個或多個男孩,但一個男孩和他喜歡地女孩成為舞伴之后就不能和其他他喜歡地女孩成為舞伴,女孩亦是如此。請問最多可以有多少對舞伴。
算法思想:
對于一個男孩子x,如果他喜歡女孩y,且y還沒有舞伴——讓他們配對
? 如果y有了舞伴,x還是會去嘗試邀請y,如果y發現她的舞伴z可以換一個舞伴,y就主動拋棄掉z(在確定z可以和別人牽手之后),和x成為舞伴
增廣路(交錯路):
路徑的起點和終點都是還沒有匹配過的點,并且路徑經過的連線是一條沒被匹配、一條已經匹配過,再下一條又沒匹配這樣交替地出現。找到這樣的路徑后,顯然路徑里沒被匹配的連線比已經匹配了的連線多一條,于是修改匹配圖,把路徑里所有匹配過的連線去掉匹配關系,把沒有匹配的連線變成匹配的,這樣匹配數就比原來多1個。
代碼:
時間復雜度:
鄰接矩陣O(n^3)
鄰接表:O(n*m)
K?nig定理
二分圖中最大匹配數等于這個圖中的最小點覆蓋數
最小點覆蓋:
每個點覆蓋以它為端點的所有邊,選擇最少的點來覆蓋所有的邊
(選最少的人,聯系到其他的所有的人)
證明:
視頻時間:01:03:42
二分圖最優匹配
存在邊權,問最大匹配下的最大邊權
KM(Kuhn-Munkres)算法
最優匹配是建立在完美匹配的基礎上的,如果不存在完美匹配,那么本算法失效(但是,我們可以人為連一些權值為0的邊,甚至加點,使得沒有匹配的節點們最后都有一個“虛假”的匹配)。
算法思路:
最開始將每個左邊節點連的權值最大的邊視為有效邊,在匹配給過程當中如果某個點找不到匹配點,則選擇將一些無效邊改成有效邊繼續去匹配,選擇的這些無效邊其實是換掉的原來的某條有效邊,那么我們肯定選換掉的代價最小的。
先讓所有人選最佳搭檔,當發生沖突時,讓降低標準最少的人來換搭檔
具體操作
給每個點預設一個頂標,只有兩個端點的頂標加起來等于邊權的邊,我們才認為是有效邊,
即L[x]+R[y]==w[x][y]的時候才是有效邊
? 最開始左集合的每個點的頂標為他連出去權值最大的邊的權值,右集合每個點頂標為0,當匹配失敗的時候,我們遍歷之前的左集合本次嘗試匹配遍歷過的點,在他們的連向右集合未遍歷的點的邊中找一個與頂標和差值最小的邊,即delta=w[x][y]-L[x]-R[y]最小的邊(可理解為找出一條損失最小的找出一條增廣路。)找到之后修改頂標:左側所有遍歷過的點減去
delta,右邊所有遍歷過的點加上delta,這樣原來的有效邊還是有效邊,而我們新加的邊也已經加上了。
? 重復找匹配+修改頂標的操作,一直到找到一個完美匹配即可
詳細過程:
降低的標準x,左邊-x,右邊+x
每個人都要為了團隊犧牲自己,甘愿降低標準
代碼:
lx[]//左端點,賦值為0x7f ly[]//右端點,賦值為0 link[i]=j//第i個人匹配的是j int dfs(int x) {visx[x]=1; for(int i=1;i<=m;i++){if(!visy[i]&&lx[x]+ly[i]==w[x][i])//有邊且為有效邊 {visy[i]=1;if(link[i]==-1||dfs(link[i])){link[i]=t;return 1;}}}return 0; } memset(ly,0,sizeof(ly)); memset(lx,0xf7,sizeof(lx)); memset(link,-1,sizeof(link)); for(int i=1;i<=n;i++) {for(int j=1;j<=n;j++){lx[i]=max(w[i][j],lx[i]);//最心動的邊 } } for(int i=1;i<=n;i++) {while(1)//全部找到完匹配才能結束 {memset(visx,0,sizeof(visx)); memset(visy,0,sizeof(visy)); if(dfs(i))break;//已經給第i人找到匹配int d=0x7f7f7f7f; for(int j=1;j<=n;j++){if(visx[j])//要降低標準的男生 {for(int k=1;k<=m;k++) {if(!visy[k])//引入新的女生 d=min(d,lx[j]+ly[k]-w[j][k]);//求最小的代價 }}}for(d==0x7f7f7f7f)return -1;//匹配失敗 for(int j=1;j<=n;j++)if(visx[j])lx[j]-=d;for(int j=1;j<=m;j++)if(visy[j])ly[j]+=d;} } //n<=m //保證所有男生都匹配(左點),女生不一定復雜度
·如果卡n^2*m可以用網絡流來做
代碼:
#include<iostream> #include<cstdio> #include<algorithm> #define N 505 #define M 250005 #define INF 9990365505 #define ll long long using namespace std; int n,m,x,y,z,tot,tim,l,r,q[N],fr[N],nxt[M],d1[M],d2[M]; int pre[N],visx[N],visy[N],mchx[N],mchy[N]; ll ex[N],ey[N],slack[N]; void add(int x,int y,int z) {tot++;d1[tot]=y;d2[tot]=z;nxt[tot]=fr[x];fr[x]=tot; } void modify(int cur) {for (int x=cur,lst;x;x=lst)lst=mchx[pre[x]],mchx[pre[x]]=x,mchy[x]=pre[x]; } void bfs(int cur) {for (int i=1;i<=n;i++)slack[i]=INF,pre[i]=0;l=1,r=0;q[++r]=cur;++tim;for (;;){while (l<=r){int u=q[l];l++;visx[u]=tim;for (int i=fr[u];i;i=nxt[i]){int v=d1[i],cost=d2[i];if (visy[v]==tim)continue;ll del=ex[u]+ey[v]-cost;if (del<slack[v]){slack[v]=del;pre[v]=u;if (!del){visy[v]=tim;if (!mchy[v]){modify(v);return;}q[++r]=mchy[v];}}}}ll del=INF;for (int i=1;i<=n;i++)if (visy[i]!=tim)del=min(del,slack[i]);l=1,r=0;for (int i=1;i<=n;i++){if (visx[i]==tim)ex[i]-=del;if (visy[i]==tim)ey[i]+=del; elseslack[i]-=del;}for (int i=1;i<=n;i++)if (visy[i]!=tim && !slack[i]){visy[i]=tim;if (!mchy[i]){modify(i);return;}q[++r]=mchy[i];}} } void KM() {for (int i=1;i<=n;i++)bfs(i);ll ans=0;for (int i=1;i<=n;i++)ans+=ex[i]+ey[i];printf("%lld\n",ans);for (int i=1;i<=n;i++)printf("%d ",mchy[i]);putchar('\n'); } int main() {scanf("%d%d",&n,&m);for (int i=1;i<=n;i++)ex[i]=-INF,ey[i]=0;for (int i=1;i<=m;i++){scanf("%d%d%d",&x,&y,&z);add(x,y,z);ex[x]=max(ex[x],(ll)z);}KM();return 0; }總結
- 上一篇: 牛客题霸 [输出二叉树的右视图] C++
- 下一篇: win10 winre是什么以及用途