[bzoj 3168] [Heoi2013]钙铁锌硒维生素

n*n 的自然数矩阵 A,B, 其中 A 满秩. 求 A 的行向量到 B 的行向量的完美匹配, 使得用 B 的任意一行替换 A 的对应行, 其他行保持不变, 矩阵仍满秩, 或报告无解. 有解输出字典序最小的方案. (1 ≤ n ≤ 300, 所有数 ≤ 10^4) 一开始理解错了题意, 以为要求 2^n 种替换方式均满秩.

B[j] 可以替换 A[i] <=> B[j] 不能被除 A[i] 以外的行线性表出

用 A 的行向量线性表出 B[j] 的方式是唯一的. 所以, 上述条件等价于以 A 的行向量为基底求出 B[j] 的坐标, 第 i 维非 0.

设 CA = B, 根据矩阵乘法的某个意义, C 的第 i 行是 B 的第 i 行以 A 的行向量为基底的坐标. 那么, 求出 C = BA^-1, 当且仅当 C[j][i] != 0, 从 A[i] 向 B[j] 连边, 求二分图的完美匹配. 方便起见, 可以在模意义下对矩阵进行运算.

题面中说 "为了避免麻烦, 如果有多种可能的答案, 请给出字典序最小的那一组", 分明是为了增加难度嘛......

我的想法是从最后一个点开始, 逆序增广. 看了 Discuss, 和标程错到一块去了......

一个可以用于 hack 的二分图:

0 1
1 0
1 2
2 0
2 1
2 2
最优解是1 0 2, 上面的算法跑出来的是1 2 0. 最后一轮增广, 2 为了和 0 匹配, 修改 1 的对象为 2, 使结果变差.

一个正确的 O(n^3) 解法: 跑一遍匈牙利. 结束后图中没有交错路, 但是可以有交错环. 把环上的边在匹配中的存在性取反, 得到另一个最大匹配. 所有其他的最大匹配都可以用一次或多次这种操作得到. 从前往后, 为每个点贪心地寻找使该点的对象尽量小的交错环, 然后从图中删掉这个点的对象. 写起来和匈牙利的 DFS 差不多.

typedef long long ll;

const int MOD = 1e9 + 7, inf = 1e9, N = 300;
int n, A[N][2*N], B[N][N];

ll fpm(ll x, int n)
{
	ll y = 1;
	for (; n; n >>= 1, (x *= x) %= MOD)
		if (n & 1)
			(y *= x) %= MOD;
	return y;
}

struct Hungary
{
	bool adj[N][N];
	int T, p[N], q[N], vis[N];
	bool dfs(int x, int z)
	{
		rep (y, 0, n) if (adj[x][y] && vis[y] < T)
		{
			vis[y] = T;
			if (p[y] == z || dfs(p[y], z))
				return q[p[y] = x] = y, true;
		}
		return false;
	}
	bool main()
	{
		int ans = 0;
		memset(p, -1, sizeof(int)*n);
		rep (i, 0, n)
		{
			++T;
			ans += dfs(i, -1);
		}
		if (ans != n) return false;
		rep (i, 0, n)
		{
			++T;
			dfs(i, i);
			vis[q[i]] = inf;
		}
		return true;
	}
} H;

bool inverse()
{
	rep (i, 0, n)
	{
		int r = i;
		while (r < n && !A[r][i]) ++r;
		if (r == n) return false;
		if (r != i) swap(A[r], A[i]);
		r = fpm(A[i][i], MOD-2);
		per (j, 2*n-1, i) A[i][j] = 1LL * A[i][j] * r % MOD;
		rep (j, 0, n) if (j != i)
		{
			r = A[j][i];
			rep (k, 0, 2*n)
				A[j][k] = ((A[j][k] - 1LL * r * A[i][k]) % MOD + MOD) % MOD;
		}
	}
	return true;
}

int main()
{
	scanf("%d", &n);
	rep (i, 0, n) rep (j, 0, n) scanf("%d", &A[i][j]);
	rep (i, 0, n) rep (j, 0, n) scanf("%d", &B[i][j]);
	rep (i, 0, n) A[i][i+n] = 1;
	if (!inverse()) return puts("NIE"), 0;
	rep (i, 0, n) rep (j, 0, n)
	{
		A[i][j] = 0;
		rep (k, 0, n) A[i][j] = (A[i][j] + 1LL * B[i][k] * A[k][j+n]) % MOD;
		if (A[i][j]) H.adj[j][i] = true;
	}
	if (H.main())
	{
		puts("TAK");
		rep (i, 0, n) printf("%d\n", H.q[i] + 1);
	}
	else
		puts("NIE");
	return 0;
}