1. 程式人生 > >學習筆記第十二節:二分圖最優匹配

學習筆記第十二節:二分圖最優匹配

正題

      看到這個題目,會覺得可以直接用綠與被綠匈牙利演算法來解決。

      但是當我們遇到,“第i個人和第j個物品會產生g[i][j]的價值,求完全匹配的最小价值”的時候。

      我們就需要用到二分圖最優匹配的演算法了。

      這個演算法的複雜度很高,要慎用

      那麼其實也是可以用費用流來解決的,直接建邊,每次跑SPFA即可。

      迴歸主題:怎麼求?

      從這裡開始認真看。設定兩個陣列,tx_ity_i,分別表示tx指左邊,ty指右邊。

      我們定義當且僅當tx_x+ty_i==g[x][i] 的時候,我們才認為從x可以到i。

bool find(int x){
	visx[x]=true;
	for(int i=1;i<=n;i++)
		if(!visy[i] && tx[x]+ty[i]==g[x][i]){//匈牙利的這一行發生改變
			visy[i]=true;
			if(prep[i]==0 || find(prep[i])){
				prep[i]=x;
				return true;
			}
		}
	return false;
}

      那麼我們要像費用流那樣子,每次優先走最大的邊。

      這樣走出來的增廣路是最大的。

      所以,每個tx_i初始化為第i個點出邊的最大,也就是tx_i=\sum_{j=1}^n max(g[i][j])

      這樣我們走出來的是最大的。

      但是可能很多個點會選擇同一個點。

       那怎麼辦呢。

       我們可以讓某些tx和ty改變使得可以走更多的邊。

       因為我們走某條邊的條件,所以明顯當時tx_i+=d,那些可以走去的右節點的ty就要-=d.

       目的是使得“之前的點可以走”。

       我們肯定要想著變化量越小越好,所以我們在去不到的邊取一個\Delta最小值。

       再不斷地用匈牙利增廣就行了。

bool find(int x){
	visx[x]=true;
	for(int i=1;i<=n;i++)
		if(!visy[i] && tx[x]+ty[i]==g[x][i]){//如果符合標杆就更新
			visy[i]=true;
			if(prep[i]==0 || find(prep[i])){
				prep[i]=x;
				return true;
			}
		}
	return false;
}

int KM(){
	memset(tx,0,sizeof(tx));
	memset(ty,0,sizeof(ty));
	memset(prep,0,sizeof(prep));
	int mmin=1e9;
	for(int i=1;i<=n;i++){
		while(1){
			memset(visx,false,sizeof(visx));
			memset(visy,false,sizeof(visy));
			if(find(i)) break;
			mmin=1e9;
			for(int j=1;j<=n;j++) if(visx[j])//更新那些去不到的點
				for(int k=1;k<=n;k++) if(!visy[k]) mmin=min(mmin,tx[j]+ty[k]-g[j][k]);
			for(int j=1;j<=n;j++) if(visx[j]) tx[j]-=mmin;//更新標杆
			for(int j=1;j<=n;j++) if(visy[j]) ty[j]+=mmin;
		}
	}
	node op=(node){0,0};
    int ans=0;
	for(int i=1;i<=n;i++) ans+=g[prep[i]][i];
	return ans;
}

       有人想問我,為什麼標杆tx和ty一開始設成0就行,因為第一次找不到之後,會更新成最大值(負數的絕對值越大,數值越小)。