[UOJ 41]【清华集训2014】矩阵变换

时间:2024-06-16 21:03:08

Description

给出一个 $N$ 行 $M$ 列的矩阵A, 保证满足以下性质:

  1. $M > N$。
  2. 矩阵中每个数都是 $[0, N]$ 中的自然数。
  3. 每行中, $[1, N]$ 中每个自然数都恰好出现一次。这意味着每行中 $0$ 恰好出现 $M - N$ 次。
  4. 每列中,$[1, N]$ 中每个自然数至多出现一次。

现在我们要在每行中选取一个非零数,并把这个数之后的数赋值为这个数。我们希望保持上面的性质4,即每列中,$[1, N]$ 中每个自然数仍然至多出现一次。

Input

第一行一个正整数 $T$,表示数据组数。

后面包含 $T$ 组数据,各组数据之间无空行。每组数据以两个正整数 $N, M$ 开始,接下来 $N$ 行,每行 $M$ 个用空格隔开的整数,意义如题所述。

Output

对于每组数据输出一行。如果有解,则输出 $N$ 个整数,依次表示每一行取的数是多少。(这应该是一个 $1$ 到 $N$ 的排列)如果无解,则输出任意卖萌表情。

Sample Input

2
5 10
0 1 0 2 3 0 0 4 0 5
2 0 3 0 0 1 0 5 4 0
4 2 1 0 0 0 3 0 5 0
0 3 0 4 0 5 0 1 2 0
1 0 0 3 2 4 5 0 0 0
5 10
0 1 0 2 3 0 0 4 0 5
2 0 3 0 0 1 0 5 4 0
4 2 1 0 0 0 3 0 5 0
0 3 0 4 0 5 0 1 2 0
1 0 0 3 2 4 5 0 0 0

Sample Output

4 5 3 1 2
5 4 3 1 2

Sample Explanation

两组输入数据是相同的。由于结果不唯一,你可以给出任意一组合法答案。

Hint

对于 20% 的数据,$M < 8, T < 8$。

对于 40% 的数据,$N < 8, T < 8$。

对于 100% 的数据,$N < 200, M < 400, T < 50$。

卖萌表情包括但不限于“\(^o^)/” (不含引号).

由于输入数据较大, 请自行优化输入方法.

时间限制:$1\texttt{s}$

空间限制:$512\texttt{MB}$

题解

稳定婚姻问题。

首先值得肯定的是每一行所选的数不能相同,那么现在就相当于 $n$ 个行匹配 $n$ 个数。

那么什么是不稳定的婚姻?如果假设数 $x$ 在第 $i$ 行选中的数之前(确保 $x$ 没有被选中的数删去),又同时选中 $x$ 的 $j$ 行中 $x$ 位置比 $i$ 行中 $x$ 位置靠前。显然这个时候是不合法的。但只要我们选第 $i$ 行的 $x$ ,再作调整就能得到合法的解。

所以归纳得出的结论就是:在一行中,偏好选在靠前面的数字。而对于每个数字,偏好其在行中的位置靠后的行。

那么就可以跑 $Gale-Shapley$ 。

 //It is made by Awson on 2018.1.18
#include <set>
#include <map>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <cstdio>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define Abs(a) ((a) < 0 ? (-(a)) : (a))
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
#define Swap(a, b) ((a) ^= (b), (b) ^= (a), (a) ^= (b))
#define writeln(x) (write(x), putchar('\n'))
using namespace std;
const int N = ;
void read(int &x) {
char ch; bool flag = ;
for (ch = getchar(); !isdigit(ch) && ((flag |= (ch == '-')) || ); ch = getchar());
for (x = ; isdigit(ch); x = (x<<)+(x<<)+ch-, ch = getchar());
x *= -*flag;
}
void write(int x) {
if (x > ) write(x/);
putchar(x%+);
} int n, m;
int alike[N+][N+], blike[N+][N+];
int achoice[N+], bchoice[N+];
queue<int>Q; void work() {
read(n), read(m);
for (int i = ; i <= n; i++)
for (int j = , tot = , x = ; j <= m; j++, x = ) {
read(x); if (x) alike[i][++tot] = x, blike[x][i] = j;
}
for (int i = ; i <= n; i++) blike[i][n+] = , bchoice[i] = n+, achoice[i] = , Q.push(i);
while (!Q.empty()) {
int a = Q.front(), b = alike[a][achoice[a]];
if (blike[b][bchoice[b]] < blike[b][a]) {
Q.pop();
if (bchoice[b] != n+) {
achoice[bchoice[b]]++;
Q.push(bchoice[b]);
}
bchoice[b] = a;
}else achoice[a]++;
}
for (int i = ; i <= n; i++) write(alike[i][achoice[i]]), putchar(' ');
}
int main() {
int t; read(t);
while (t--) {work(); if (t) putchar('\n'); }
return ;
}