http://poj.org/problem?id=3686
拆点+KM算法
题目大意:
n个玩具在m台机器上完成所需时间个不同
一台机器只有完成一个玩具的的制作才能继续完成其它的
问你n给玩具完成制作最小平均时间
思路转自
http://blog.sina.com.cn/s/blog_6af663940100mw9t.html
此题构图很巧妙。设n个订单的执行时间分别为t1,t2…tn,则n个订单的总的执行时间是
t1*n+t2*(n-1)+t3*(n-2)+…+tn-1*2+tn。将每个机器j拆成n个点,第k个点表示倒数第k个订单在此机器上完成,连边权值为:tmp[i][j]*k。这样就转换成了求二分图最小权匹配的问题了。KM算法,把权值设为负值求最大权匹配
求的是最小平均时间 把时间转换为负的 就可以求最大匹配了
我个人直接用三维数组储存的,这样原来的右组就由一维变成了二维
其它的都一样了
代码及其注释:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int MAX=0x7ffffff;
const int N=51;
int paytime[N][N][N];//拆点后花费时间,
int a[N];//左组顶标
int b[N][N];//右组顶标
bool lv[N];//左组是否在交叉树内
bool rv[N][N];//右组是否在交叉树内
int n,m;
int f[N][N];//右组指向
bool dfs(int x)//匈牙利算法找匹配
{
lv[x]=true;
for(int i=1;i<=m;++i)
{
for(int j=1;j<=n;++j)
{
if(!rv[i][j]&&a[x]+b[i][j]==paytime[x][i][j])
{
rv[i][j]=true;
if(f[i][j]==-1||dfs(f[i][j]))
{
f[i][j]=x;
return true;
}
}
}
}
return false;
}
int KM()
{
memset(b,0,sizeof(b));
for(int i=1;i<=n;++i)
{
a[i]=-MAX;
for(int j=1;j<=m;++j)
{
for(int l=1;l<=n;++l)
{
a[i]=max(a[i],paytime[i][j][l]);//左组顶标初始最大
}
}
}
memset(f,-1,sizeof(f));
for(int w=1;w<=n;++w)
{
while(1)
{
memset(lv,false,sizeof(lv));
memset(rv,false,sizeof(rv));
if(dfs(w))//匹配的话直接退出循环找下一个 否则减d继续找
break;
int d=MAX;
for(int i=1;i<=n;++i)
{
if(lv[i])
{
for(int j=1;j<=m;++j)
{
for(int l=1;l<=n;++l)
{
if(!rv[j][l])
{
d=min(d,a[i]+b[j][l]-paytime[i][j][l]);//找最小变化量
}
}
}
}
}
for(int i=1;i<=n;++i)
{
if(lv[i])
a[i]-=d;
}
for(int j=1;j<=m;++j)
{
for(int l=1;l<=n;++l)
{
if(rv[j][l])
b[j][l]+=d;
}
}
}
}
int sum=0;
for(int j=1;j<=m;++j)
{
for(int l=1;l<=n;++l)
{
if(f[j][l]!=-1)
sum-=paytime[f[j][l]][j][l];//最优匹配总值
}
}
return sum;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
int k;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
scanf("%d",&k);
for(int l=1;l<=n;++l)
{
paytime[i][j][l]=-(k*l);//拆点
}
}
}
printf("%.6f\n",1.0*(KM())/n);
}
return 0;
}
浙公网安备 33010602011771号