HDU6346
Problem Description
度度熊有一个可能是整数规划的问题:
给定 n×n 个整数 ai,j(1≤i,j≤n),要找出 2n 个整数 x1,x2,…,xn,y1,y2,…,yn 在满足 xi+yj≤ai,j(1≤i,j≤n) 的约束下最大化目标函数 ∑ni=1xi+∑ni=1yi,
你需要帮他解决这个整数规划问题,并给出目标函数的最大值。
Input
第一行包含一个整数 T,表示有 T 组测试数据。
接下来依次描述 T 组测试数据。对于每组测试数据:
第一行包含一个整数 n,表示该整数规划问题的规模。
接下来 n 行,每行包含 n 个整数,其中第 i 行第 j 列的元素是 ai,j。
保证 1≤T≤20,1≤n≤200,−109≤ai,j≤109(1≤i,j≤n)。
Output
对于每组测试数据,输出一行信息 "Case #x: y"(不含引号),其中 x 表示这是第 x 组测试数据,y 表示目标函数的最大值,行末不要有多余空格。
Sample Input
2 1 0 2 1 2 3 4
Sample Output
Case #1: 0 Case #2: 5
这是二分图的最优完备匹配问题,也就是km(Kuhn——Munkres)算法的模版题。
从n=2看起:
目标函数:x1+x2+y1+y2
把x和y进行匹配可得两种情况:1. x1,y1和x2,y2 ; 2. x1,y2和x2,y1
对于第一种,最大有:a11+a22;
对于第二种,最大有:a12+a21;
因为对于任意的i、j ,都要有 xi+yj≤aij , 所以所求值应该为:min { a11+a22 ,a12+a21 }
二分图:即对于一个图中的所有顶点,可以将所有顶点分成两个互不相交的集合A、B。集合A和集合B中的点连接形成图的边,但同一集合内的点不能互相连接。
km算法:是 匈牙利算法的扩展 ,用于求二分图的最大权值完美匹配。
( km算法网上有很多讲解都讲得挺好的,通过一些像是 “男女匹配找对象”、“做工作” 的例子可以很轻松的理解。但具体数学上证明尚未深入了解......)
这里求的是最小值,只需将每条边权值取相反数,得到最大值后再取相反数即可。
代码:(dfs实现)
这里的dfs模版要够硬才过得去,不然很容易就TLE。首先是用slack函数来优化时间复杂度到O(n3),然后还需要通过在深搜加上一个flag参数做优化,然后在函数km()里做判断来减少匹配的次数。也可以用bfs来做。
//
// main.cpp
// P1003
//
// Created by jinyu on 2018/8/10.
// Copyright © 2018年 jinyu. All rights reserved.
//
#include <iostream>
#include <algorithm>
using namespace std;
#define max(a,b) a>b?a:b
#define min(a,b) a<b?a:b
const int MAXN = 200+7;
const int INF = 0x3f3f3f3f;
int N;
int axy[MAXN][MAXN];
int ex_X[MAXN];
int ex_Y[MAXN];
int match[MAXN];
int slack[MAXN];
bool visX[MAXN];
bool visY[MAXN];
inline bool dfs(int x , int flag){
visX[x] = true;
for(int i = 0;i<N;++i){
if(visY[i]) continue;
int temp = ex_X[x] + ex_Y[i] - axy[x][i];
if(temp==0){
visY[i] = true;
if(match[i]==-1 || dfs(match[i],flag)){
if(flag) match[i] = x;
return true;
}
}
else
slack[i] = min( slack[i] , temp );
}
return false;
}
long long km(){
memset(match , -1 , sizeof(match));
memset(ex_Y , 0 , sizeof(ex_Y));
for(int i = 0;i<N;++i){
ex_X[i] = axy[i][0];
for(int j = 1;j<N;++j){
ex_X[i] = max( ex_X[i] , axy[i][j] );
}
}
for(int i = 0;i<N;++i){
memset(visY, false, sizeof(visY));
memset(visX, false, sizeof(visX));
for(int j = 0;j<N;++j) slack[j] = INF;
if(dfs(i,1)) continue;
bool isOK = true;
while(isOK){
int tempDelta = INF;
for(int i = 0;i<N;++i){
if(!visY[i] && tempDelta > slack[i])
tempDelta = slack[i] ;
}
for(int i = 0;i<N;++i){
if(visX[i])
ex_X[i] -= tempDelta;
if(visY[i])
ex_Y[i] += tempDelta;
else
slack[i] -= tempDelta;
}
for(int i = 0;i<N;++i){
if(!visY[i] && slack[i]==0){
visY[i] = true;
if(match[i]==-1 || dfs(match[i],0)){
isOK = false;
break;
}
}
}
}
memset(visY, false, sizeof(visY));
memset(visX, false, sizeof(visX));
dfs(i,1);
}
long long res = 0;
for(int i = 0;i<N;i++)
res += axy[match[i]][i];
return res;
}
int main(){
int T;
scanf("%d",&T);
int tt = T;
while(tt--){
scanf("%d",&N);
for(int i = 0;i<N;i++){
for(int j = 0;j<N;j++){
scanf("%d",&axy[i][j]);
axy[i][j]*=-1;
}
}
printf("Case #%d: %lld\n",T-tt,-km());
}
}