 /**//*
題意:有N個(gè)工件要在M個(gè)機(jī)器上加工,有一個(gè)N*M的矩陣描述其加工時(shí)間。
同一時(shí)間內(nèi)每個(gè)機(jī)器只能加工一個(gè)工件,問(wèn)加工完所有工件后,使得平均加工時(shí)間最小。

將工件作為二分圖中X部的點(diǎn),總共N個(gè)。
將每個(gè)機(jī)器拆成N個(gè)點(diǎn)作為二分圖中Y部的點(diǎn),總共N*M個(gè)。
第J個(gè)機(jī)器的第P個(gè)點(diǎn)代表,使用機(jī)器 J進(jìn)行倒數(shù)第P次加工。
假設(shè)我們按順序在J機(jī)器上工件I1,I2,I3..IK個(gè)工件,則總共需要花費(fèi)I1*K+I2*(K- 1)+I3*(K-3)+ +IK。
所以我們對(duì)于X中每個(gè)點(diǎn)I,Y中每個(gè)點(diǎn)(J,P),連接一條A[I,J]*P權(quán)值的邊。
接下來(lái)進(jìn)行二分圖最佳匹配即可。
*/
#include<cstdio>
#include<cstring>

const int MAXN=55;
const int INF=1000000000;

int n,m;
int g[MAXN][MAXN*MAXN];
int data[MAXN][MAXN];
int lx[MAXN],ly[MAXN*MAXN];//可行頂標(biāo)
int cx[MAXN],cy[MAXN*MAXN];//匹配邊集
bool sx[MAXN],sy[MAXN*MAXN];

 bool path(int u) {//擴(kuò)展二分圖 判斷是否存在由行元素u出發(fā)的可增廣路徑
sx[u]=1;
 for(int v=1;v<=m;v++) {
 if(g[u][v]==lx[u]+ly[v]&&!sy[v]) {
sy[v]=1;
 if(cy[v]==0||path(cy[v])) {
cx[u]=v;cy[v]=u;
return true;
}
}
}
return false;
}
 int KM() {
memset(ly,0,sizeof(ly));
memset(cx,0,sizeof(cx));
memset(cy,0,sizeof(cy));
 for(int i=1;i<=n;i++) {
lx[i]=-INF;
for(int j=1;j<=m;j++)
if(lx[i]<g[i][j])lx[i]=g[i][j];
}
 for(int u=1;u<=n;u++) {
memset(sx,0,sizeof(sx));
memset(sy,0,sizeof(sy));
 while(!path(u)) {
int Min=INF;
 for(int i=1;i<=n;i++) {
if(!sx[i])continue;
for(int j=1;j<=m;j++)
if(!sy[j]&&lx[i]+ly[j]-g[i][j]<Min)
Min=lx[i]+ly[j]-g[i][j];
}
//調(diào)整頂標(biāo),二分圖撤去該元素
for(int i=1;i<=n;i++)
 if(sx[i]) {lx[i]-=Min;sx[i]=false;}
for(int i=1;i<=m;i++)
 if(sy[i]) {ly[i]+=Min;sy[i]=false;}
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans+=g[i][cx[i]];
return ans;
}
 int main() {
int T;
scanf("%d",&T);
 while(T--) {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&data[i][j]);
//build
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=n;k++)
g[i][(j-1)*n+k]=-data[i][j]*k;//花費(fèi)
m=n*m;
double ans=-1.0*KM()/n;
printf("%.6f\n",ans);
}
return 0;
}
|
|
常用鏈接
隨筆分類
Links
搜索
最新評(píng)論

|
|