給定 m 個長度為 n 的排列,求一個排列 t,使其與這 m 個排列的距離之和最小。距離定義為:dist = Σ|aᵢ - bᵢ|。
匹配邊 i→j 表示數值 j 放在位置 i,一個完美匹配對應一個排列,最大權匹配對應最小距離和。
const int maxn = 100 + 10;
const int INF = 0x3f3f3f3f;
int nx, ny; // 兩邊點數
int g[maxn][maxn]; // 二分圖邊權
int linker[maxn], lx[maxn], ly[maxn]; // 匹配與頂標
int slack[maxn];
bool visx[maxn], visy[maxn];
bool DFS(int x) {
visx[x] = true;
for (int y = 1; y <= ny; y++) {
if (visy[y]) continue;
int tmp = lx[x] + ly[y] - g[x][y];
if (tmp == 0) { // 在相等子圖中
visy[y] = true;
if (linker[y] == -1 || DFS(linker[y])) {
linker[y] = x;
return true;
}
} else if (slack[y] > tmp) {
slack[y] = tmp; // 記錄最小 slack
}
}
return false;
}
int KM() {
memset(linker, -1, sizeof(linker));
memset(ly, 0, sizeof(ly));
// 初始化頂標:lx 為各行最大值
for (int i = 1; i <= nx; i++) {
lx[i] = -INF;
for (int j = 1; j <= ny; j++)
if (g[i][j] > lx[i]) lx[i] = g[i][j];
}
for (int x = 1; x <= nx; x++) {
fill(slack + 1, slack + ny + 1, INF);
while (true) {
memset(visx, false, sizeof(visx));
memset(visy, false, sizeof(visy));
if (DFS(x)) break;
// 修改頂標
int d = INF;
for (int i = 1; i <= ny; i++)
if (!visy[i] && d > slack[i]) d = slack[i];
for (int i = 1; i <= nx; i++)
if (visx[i]) lx[i] -= d;
for (int i = 1; i <= ny; i++) {
if (visy[i]) ly[i] += d;
else slack[i] -= d;
}
}
}
int res = 0;
for (int i = 1; i <= ny; i++)
if (linker[i] != -1) res += g[linker[i]][i];
return res;
}
int a[maxn][maxn];
int main() {
int T, n, m;
scanf("%d", &T);
for (int cas = 1; cas <= T; cas++) {
scanf("%d%d", &n, &m);
nx = ny = n;
// 讀入 m 個排列
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
scanf("%d", &a[i][j]);
// 構建邊權矩陣
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) {
g[i][j] = 0;
for (int k = 1; k <= m; k++)
g[i][j] -= abs(a[k][i] - j);
}
printf("Case #%d: %d\n", cas, -KM());
}
return 0;
}