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; }