1. 程式人生 > >[SDOI2017]新生舞會,洛谷P3705,分數規劃+二分圖最優匹配

[SDOI2017]新生舞會,洛谷P3705,分數規劃+二分圖最優匹配

正題

      題目連結點這裡

      給出兩個矩陣a,b,都表示i和j之間的權值,要求構造一個排列P,使得\frac{\sum_{i=1}^na_{i,pi}}{\sum_{i=1}^nb_{i,pi}}最大。

      我們來二分一個mid,使得\frac{\sum_{i=1}^na_{i,p_i}}{\sum_{i=1}^nb_{i,p_i}}>=mid,然後變形金剛,

      \\\sum_{i=1}^na_{i,p_i}>=mid\sum_{i=1}^nb_{i,p_i} \\ \to\sum_{i=1}^na_{i,p_i}>=\sum_{i=1}^nb_{i,p_i}*mid \\ \to\sum_{i=1}^na_{i,p_i}-b_{i,p_i}*mid>=0

      那麼我們就構造一個排列P使得\sum_{i=1}^na_{i,p_i}-b_{i,p_i}*mid>=0

      發現是一個二分圖帶權匹配,因為相當於從i到j建一條權為a_{i,j}-b_{i,j}*mid的邊。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

int n;
int a[110][110],b[110][110];
double g[110][110];
double tx[110],ty[110];
int prep[110];
bool visx[110],visy[110];

bool find(int x){
	visx[x]=true;
	for(int i=1;i<=n;i++)
		if(tx[x]+ty[i]==g[x][i] && !visy[i]){
			visy[i]=true;
			if(prep[i]==0 || find(prep[i])){
				prep[i]=x;
				return true;
			}
		}
	return false;
}

double KM(){
	memset(tx,0,sizeof(tx));
	memset(ty,0,sizeof(ty));
	memset(prep,0,sizeof(prep));
	double 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;
		}
	}
	double ans=0;
	for(int i=1;i<=n;i++) ans+=g[prep[i]][i];
	return ans;
}

bool check(double x){
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			g[i][j]=a[i][j]-b[i][j]*x;
	return KM()>=0;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&a[i][j]);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			scanf("%d",&b[i][j]);
	double l=0,r=1e4;
	while(r-l>=1e-7){
		double mid=(l+r)/2;
		if(check(mid)) l=mid;
		else r=mid;
	}
	printf("%lf",l);
}