题意
给出一个 (ntimes n) 的矩阵 (A),你可以进行下述操作任意多次:指定整数 (k)((1le kle n)),使 (A_{ni}) 与 (A_{in}) 交换。
求你能得到的字典序最小的矩阵大小。
(nle 1000)
解析
不难发现操作是可逆的,并且操作的顺序并不影响结果。那么我们只需要决定要对哪些 (k) 进行操作。不妨定义 bool 变量 (T_i),当 (T_i=bold{true}),表示要操作 (k),反之不操作 (k)。
首先字典序能够想到贪心,即从左往右、从上往下依次使每个位置上的数尽可能小。那么哪些数可能出现在 ((i,j))?可以发现任何调换都是关于主对角线反转,于是只可能有 (A_{ij}) 和 (A_{ji}) 出现在 ((i,j))。
那么怎样让 (A_{ij}) 最终在 ((i,j)) ?显然只有 (k=i) 或 (k=j) 对 ((i,j)) 有影响,稍微推导可以知道结果是 (T_i=T_j)。而让 (A_{ji}) 最终在 ((i,j)) 则要求 (T_ineq T_j)。
那么可以看出这是一个 2-sat 问题。再回到本题的具体解决方法,从上到下从左往右地枚举右上侧的位置(原因即字典序),如果 (A_{ij}=A_{ji}),则没有任何限制;如果 (A_{ij}>A_{ji}) 则要求 (T_ineq T_j);如果 (A_{ij}< A_{ji}) 则要求 (T_i=T_j)。
当然可能无法满足要求,说明无法在不影响先前的位置的前提下使当前位置最小,又根据字典序的性质,此时不能满足当前位置最小,否则可以满足。
那怎么判断 2-sat 问题是否有解?Tarjan?那么边数以及进行 Tarjan 的次数都是 (O(n^2)),(O(n^4)) 显然不能过。只会添加条件?强连通缩点!然而每次添加条件并不会保证强连通数量严格减少,复杂度依然错误。
那么此时需要关注条件形式——相等或不等。那么我们可以这样说,两个变量之间要么没有关联,要么一个变量决定后,另一个变量必然确定。于是延申出下述方案——
把可以确定相等的变量缩点,并且记录与这个变量不等的变量是哪一个。例如现在有两个不相干的变量 (a,b)((a,b) 分别代表一个缩点),(a',b') 代表与 (a,b) 不等的变量。如果现在确定 (aneq b),则 (a'=b),(b'=a),可以进一步缩点;如果确定 (a=b),则 (a=b) 且 (a'=b')。
上面是不相干的变量,如果是相关的变量 (a,b),此时要么合法,不会产生新的缩点,要么不合法。例如,如果有要求 (a=b),而我们发现 (a'=b)(准确的说,(b) 在 (a') 这个缩点中),此时就发生了矛盾。
最后,怎么缩点?只缩不拆,并查集。
源代码
#include <cstdio>
#include <cstring>
#include <algorithm>
const int MAXN = 1005;
struct Dsu
{
int fa[MAXN], dif[MAXN];
void clear(const int &siz)
{
for (int i = 1; i <= siz; ++i)
{
fa[i] = i;
dif[i] = -1;
}
}
int findFa(const int &src)
{
return fa[src] == src ? src : fa[src] = findFa(fa[src]);
}
int combine(int src, int dst)
{
src = findFa(src), dst = findFa(dst);
return fa[src] = dst;
}
void linkSame(int ele_a, int ele_b)
{
ele_a = findFa(ele_a), ele_b = findFa(ele_b);
if (ele_a == ele_b) /* useless */
{
return;
}
if (ele_a == dif[ele_b]) /* corrupt */
{
return;
}
int tmp_ele = combine(ele_a, ele_b), tmp_dif = -1;
if ((~dif[ele_a]) && (~dif[ele_b]))
{
tmp_dif = combine(dif[ele_a], dif[ele_b]);
}
else
{
if (~dif[ele_a])
{
tmp_dif = dif[ele_a];
}
if (~dif[ele_b])
{
tmp_dif = dif[ele_b];
}
}
dif[tmp_ele] = tmp_dif;
if (~tmp_dif)
{
dif[tmp_dif] = tmp_ele;
}
}
void linkDiff(int ele_a, int ele_b)
{
ele_a = findFa(ele_a), ele_b = findFa(ele_b);
if (ele_a == dif[ele_b]) /* useless */
{
return;
}
if (ele_a == ele_b) /* corrupt */
{
return;
}
int tmp_ele_a = ele_a, tmp_ele_b = ele_b;
if (~dif[ele_a])
{
tmp_ele_b = combine(tmp_ele_b, dif[ele_a]);
}
if (~dif[ele_b])
{
tmp_ele_a = combine(tmp_ele_a, dif[ele_b]);
}
dif[tmp_ele_a] = tmp_ele_b;
dif[tmp_ele_b] = tmp_ele_a;
}
};
Dsu same_block;
int mat[MAXN][MAXN], rev_tag[MAXN];
void doSwap(const int &siz)
{
same_block.clear(siz);
for (int rol = 1; rol <= siz; ++rol)
{
for (int col = rol + 1; col <= siz; ++col)
{
if (mat[rol][col] != mat[col][rol])
{
if (mat[rol][col] < mat[col][rol])
{
same_block.linkSame(rol, col);
}
else
{
same_block.linkDiff(rol, col);
}
}
}
}
}
void getAnswer(const int &siz)
{
std::fill(rev_tag + 1, rev_tag + 1 + siz, -1);
for (int i = 1; i <= siz; ++i)
{
int rt_i = same_block.findFa(i);
if (rev_tag[rt_i] == -1)
{
rev_tag[rt_i] = 1;
rev_tag[same_block.dif[rt_i]] = 0;
}
}
for (int i = 1; i <= siz; ++i)
{
if (rev_tag[same_block.findFa(i)])
{
for (int j = 1; j <= siz; ++j)
{
std::swap(mat[i][j], mat[j][i]);
}
}
}
}
void solveCase()
{
int siz;
scanf("%d", &siz);
for (int rol = 1; rol <= siz; ++rol)
{
for (int col = 1; col <= siz; ++col)
{
scanf("%d", &mat[rol][col]);
}
}
doSwap(siz);
getAnswer(siz);
for (int rol = 1; rol <= siz; ++rol)
{
for (int col = 1; col < siz; ++col)
{
printf("%d ", mat[rol][col]);
}
printf("%dn", mat[rol][siz]);
}
}
int main()
{
int cnt_case;
scanf("%d", &cnt_case);
while (cnt_case--)
{
solveCase();
}
return 0;
}