<span style="background-color: rgb(255, 255, 255); font-family: Arial, Helvetica, sans-serif;"></span><pre name="code" class="html">//664K 16MS G++
// 696K 16MS G++ with slack
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;
#define MAX 110
int map[MAX][MAX]; // 0: space, even: human odd: house
int N; // number of rows, height
int M; // number of columns. width
struct Point {
int x;
int y;
};
typedef struct Point Human;
typedef struct Point House;
Human humanInfo[MAX];
House houseInfo[MAX];
int houseDistance[MAX][MAX]; // record the min distance from each man to each house
char BFSFlag[MAX][MAX];
struct BFSNode {
int x;
int y;
int distance;
};
queue<BFSNode> BFSQueue;
int humanNum;
int houseNum;
#define HUMAN 'm'
#define HOUSE 'H'
// if node is a house, return 1
char pushBFSNode(int newX, int newY, int newDistance, int curNodeId) {
BFSFlag[newX][newY] = 1;
BFSNode newNode;
newNode.x = newX;
newNode.y = newY;
newNode.distance = newDistance + 1;
BFSQueue.push(newNode);
if (map[newX][newY] > 0 && map[newX][newY]%2 == 1) { // if it is a house
int houseId = map[newX][newY]/2 +1;
houseDistance[curNodeId][houseId] = -(newDistance + 1); // get min, so add minus
return 1;
}
return 0;
}
void getSomeoneDistance(int beginId) {
while(BFSQueue.size()) {
BFSQueue.pop();
}
int reachedHouseNum = 0;
memset(BFSFlag, 0, sizeof(BFSFlag));
int beginX = humanInfo[beginId].x;
int beginY = humanInfo[beginId].y;
BFSNode beginNode;
beginNode.x = beginX;
beginNode.y = beginY;
beginNode.distance = 0;
BFSFlag[beginX][beginY] = 1;
BFSQueue.push(beginNode);
while(BFSQueue.size()) {
BFSNode curNode = BFSQueue.front();
BFSQueue.pop();
int curX = curNode.x;
int curY = curNode.y;
int curDistance = curNode.distance;
//up
if (curY < N -1 && !BFSFlag[curX][curY+1]) {
reachedHouseNum += pushBFSNode(curX, curY+1, curDistance, beginId);
}
//down
if (curY > 0 && !BFSFlag[curX][curY-1]) {
reachedHouseNum += pushBFSNode(curX, curY-1, curDistance, beginId);
}
//left
if (curX > 0 && !BFSFlag[curX-1][curY]) {
reachedHouseNum += pushBFSNode(curX-1, curY, curDistance, beginId);
}
//right
if (curX < M -1 && !BFSFlag[curX+1][curY]) {
reachedHouseNum += pushBFSNode(curX+1, curY, curDistance, beginId);
}
if (reachedHouseNum >= houseNum) {
return;
}
}
}
#define INF 999999
int lx[MAX];
int ly[MAX];
int slack[MAX];
int V1[MAX];
int V2[MAX];
int V1Status[MAX];
int V2Status[MAX]; // 0: V1 V2 ethier no in macth 1: V1 in match, V2 not in match 2: V1 V2 both in the macth
char waittingMove[MAX];
char getPair(int curId) {
// printf("getPair %d\n", curId);
V1Status[curId] = 1; // curId is in alternating path
for (int i = 1; i <= houseNum; i++) {// check all house
int d = lx[curId] + ly[i] - houseDistance[curId][i];
slack[i] = slack[i] < d ? slack[i]: d;
if (lx[curId] + ly[i] == houseDistance[curId][i]) { // if satisfy the equal
V2Status[i] = 1; // i is in alternating path
if (!V2[i]) { // if i is not assigned yet
V1[curId] = i;
V2[i] = curId;
return 1; // find best paired
} else {
if (!waittingMove[V2[i]]) {
waittingMove[V2[i]] = 1; // try to move i's owner
if (getPair(V2[i])) { // if move success
V1[curId] = i;
V2[i] = curId;
return 1;
}
}
}
}
}
return 0;
}
void adjustD() {
int minD = INF;
for (int i = 1; i <= houseNum; i++) {
if (!V2Status[i]) { // if the Y not in match, but X in match
minD = minD < slack[i] ? minD : slack[i];
}
}
// for (int i = 1; i <= humanNum; i++) { // get minD, austerity method
// if(V1Status[i]) {
// for (int j = 1; j <= houseNum; j++) {
// if (!V2Status[j]) {
// int tmp = lx[i] + ly[j] - houseDistance[i][j];
// minD = minD < tmp ? minD : tmp;
// }
// }
// }
// }
for (int i = 1; i <= humanNum; i++) {
if (V1Status[i]) {
lx[i] -= minD;
}
}
for (int i = 1; i <= houseNum; i++) {
if (V2Status[i]) {
ly[i] += minD;
}
if (slack[i] != INF) { // if not a INF, - minD
slack[i] -= minD;
}
}
}
int getMaxW(int curNodeId) {
int maxW = -INF;
for (int i = 1; i <= humanNum; i++) {
if (houseDistance[curNodeId][i] < 0) { // connected
maxW = maxW > houseDistance[curNodeId][i] ? maxW : houseDistance[curNodeId][i];
}
}
return maxW;
}
void KM() {
for (int i = 1; i <= humanNum; i++) { // init lx
lx[i] = getMaxW(i);
}
memset(ly, 0, sizeof(ly)); // init ly
memset(V1, 0, sizeof(V1));
memset(V2, 0, sizeof(V2));
for (int i = 1; i <= humanNum; i++) {
// printf("KM %d\n", i);
for (int j = 1; j <= MAX; j++) { // init slack
slack[j] = INF;
}
while (1) { // endless try until get i's best pair
memset(waittingMove, 0, sizeof(waittingMove));
memset(V1Status, 0, sizeof(V1Status));
memset(V2Status, 0, sizeof(V2Status));
if (getPair(i)) { // if find a best pair, quit
break;
}
adjustD(); // if not get pair, adjust the lx and ly
}
}
int sum = 0;
for (int i = 1; i <= humanNum; i++) {
sum += houseDistance[i][V1[i]];
}
printf("%d\n", -sum);
}
void getDistance() {
for (int i = 1; i <= humanNum; i++) {
getSomeoneDistance(i);
}
// for (int i = 1; i <= humanNum; i++) {
// for (int j = 1; j <= houseNum; j++) {
// printf("man: %d %d -> house: %d %d %d\n",
// humanInfo[i].x, humanInfo[i].y, houseInfo[j].x, houseInfo[j].y
// , houseDistance[i][j]);
// }
// }
KM();
}
void solve() {
getDistance();
}
int main() {
while(1) {
scanf("%d %d", &N, &M);
if (!N && !M) {
return 0;
}
char row[MAX] = "";
houseNum = 0;
humanNum = 0;
memset(map, 0, sizeof(map));
memset(humanInfo, 0, sizeof(humanInfo));
memset(houseInfo, 0, sizeof(houseInfo));
memset(houseDistance, 0, sizeof(houseDistance));
for (int i = 0; i < N; i++) {
scanf("%s", row);
for (int j = 0; j < M;j++ ) {
if (row[j] == HUMAN) {
map[j][i] = (++humanNum)*2; // human Id begin from 2
humanInfo[humanNum].x = j;
humanInfo[humanNum].y = i;
} else if (row[j] == HOUSE) {
map[j][i] = (houseNum++)*2 + 1; // house Id begin from 1;
houseInfo[houseNum].x = j;
houseInfo[houseNum].y = i;
}
}
}
solve();
}
}
<span style="background-color: rgb(255, 255, 255); font-family: Arial, Helvetica, sans-serif;">KM算法小变形题,从权的和最大变成了权最小。</span>
本题难不在于转化,纯粹是难在转化,一开始要BFS求出每个man到每个house的最短距离,
然后再用KM算法求出最佳匹配。
KM算法第一次写,在匈牙利算法基础上增加些判断于flag即可,
首先,本题说明了man和house的数量相等,保证了最优匹配的存在,那么要求最小的权和,而KM能求的却是最大的权和,
这时候需要变形一下,因为保证了man到house的距离都是正数,因此可以将所有的权值变为对应的负数,然后求这些权值中的最优匹配即可(在权值全部是正数的情况下, 负数和最大 -> 正数和最小)。KM算法的流程:
两个点集V1(对应man) V2(对应house):
首先要设置两个顶标数组lx(for V1)和ly(for V2),初始值:
lx[i] 定义为V1的点 i 到V2的所有边的最大权值wMAX。
ly[i] 初始值均为0.
还需要一个slack数组来记录V2每个点对应的d,初始为INF
然后就是匈牙利算法的流程,对V1的每个点i求增广路得到在V2中的最佳匹配,
对每个点求最佳匹配前,要设置一些额外数组来保存信息:
V1status[] 来标示V1的某个点i是都在此次查找的增广路中,
V2status[] 来标示V2的某个点i是否在此次查找的增广路中,
每次调用getPair(i)来求最佳匹配时,先将V1status[i]置为1表示i在此次查找的增广路中。
然后每check一条边(i -> j),可以顺便的设置其slack[j]为 min(slack[j], lx[i]+ly[j] - w(i,j)).
匹配的条件除了i与j之间有边以外,还要增加一条 即lx[i] + ly[j] = w(i, j), 满足此条件的j 也要设置V2status[j] = 1.
然后查看j是否已经被配过对了,如果没有,皆大欢喜,直接返回,为下一个V1的点配对,
如果j配过对了,那么按照匈牙利算法的规则,尝试 “挪” j原来的配对者到另外一个位置,即调用getpair(V2[j])。
如果最后对i点还找不到一个最优匹配,那么进行lx和ly的调整:
首先遍历所有V2status[x] == 0(即不在上面配对过程中所经过的增广路中)的V2的点的slack值,找到最小的slack作为D。
然后遍历lx[], 对所有V1status[x] == 1(在增广路中)的V1点i lx[i] -= D.
遍历所有的ly[] 对所有V2status[x] == 0 (不在增广路中)的V2点 ly[i] += D.
最后,还要对slack的每个成员 -=D。
然后再次开始对点i进行最优配对,如果还没有,继续调整,直到最后找到为止。
最后,在每个V1点的最优匹配都找到以后,累加最优匹配的每条边的权值取反即可。
本题数据弱,用slack优化 和 直接朴素(遍历每条边)求D的时间是一样的.