题意

给出一个 \(n\times n\) 的矩阵 \(A\),你可以进行下述操作任意多次:指定整数 \(k\)(\(1\le k\le n\)),使 \(A_{ni}\) 与 \(A_{in}\) 交换。

求你能得到的字典序最小的矩阵大小。

\(n\le 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_i\neq T_j\)。

那么可以看出这是一个 2-sat 问题。再回到本题的具体解决方法,从上到下从左往右地枚举右上侧的位置(原因即字典序),如果 \(A_{ij}=A_{ji}\),则没有任何限制;如果 \(A_{ij}>A_{ji}\) 则要求 \(T_i\neq 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\) 不等的变量。如果现在确定 \(a\neq 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("%d\n", mat[rol][siz]);
    }
}
int main()
{
    int cnt_case;
    scanf("%d", &cnt_case);
    while (cnt_case--)
    {
        solveCase();
    }
    return 0;
}

「postOI」Cross Swapping的更多相关文章

  1. 「SCOI2016」妖怪 解题报告

    「SCOI2016」妖怪 玄妙...盲猜一个结论,然后过了,事后一证,然后假了,数据真水 首先要最小化 \[ \max_{i=1}^n (1+k)x_i+(1+\frac{1}{k})y_i \] \ ...

  2. 「SCOI2015」小凸想跑步 解题报告

    「SCOI2015」小凸想跑步 最开始以为和多边形的重心有关,后来发现多边形的重心没啥好玩的性质 实际上你把面积小于的不等式列出来,发现是一次的,那么就可以半平面交了 Code: #include & ...

  3. 「NOI2014」购票 解题报告

    「NOI2014」购票 写完了后发现写的做法是假的...然后居然过了,然后就懒得管正解了. 发现需要维护凸包,动态加点,询问区间,强制在线 可以二进制分组搞,然后你发现在树上需要资瓷撤回,然后暴力撤回 ...

  4. 「SDOI2014」向量集 解题报告

    「SDOI2014」向量集 维护一个向量集合,在线支持以下操作: A x y :加入向量 \((x, y)\): Q x y l r:询问第 \(L\) 个到第 \(R\) 个加入的向量与向量 \(( ...

  5. LOJ #2205. 「HNOI2014」画框 解题报告

    #2205. 「HNOI2014」画框 最小乘积生成树+KM二分图带权匹配 维护一个\((\sum A,\sum B)\)的匹配下凸包,答案在这些点中产生. 具体的,凸包两端可以直接跑单独的\(A\) ...

  6. [LOJ 2039] 「SHOI2015」激光发生器

    [LOJ 2039] 「SHOI2015」激光发生器 链接 链接 题解 分为两个部分 第一个是求直线之间的交点找到第一个触碰到的镜面 第二个是求直线经过镜面反射之后的出射光线 第一个很好做,第二个就是 ...

  7. 「译」JUnit 5 系列:条件测试

    原文地址:http://blog.codefx.org/libraries/junit-5-conditions/ 原文日期:08, May, 2016 译文首发:Linesh 的博客:「译」JUni ...

  8. 「译」JUnit 5 系列:扩展模型(Extension Model)

    原文地址:http://blog.codefx.org/design/architecture/junit-5-extension-model/ 原文日期:11, Apr, 2016 译文首发:Lin ...

  9. JavaScript OOP 之「创建对象」

    工厂模式 工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程.工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题. function createPers ...

  10. 「C++」理解智能指针

    维基百科上面对于「智能指针」是这样描述的: 智能指针(英语:Smart pointer)是一种抽象的数据类型.在程序设计中,它通常是经由类型模板(class template)来实做,借由模板(tem ...

随机推荐

  1. 【分析笔记】全志平台 gpio_wdt 驱动应用和 stack crash 解决

    使用说明 第一次遇到看门狗芯片是通过切换电平信号来喂狗,如 SGM706 芯片,之前也比较少会用到看门狗芯片.原本打算参考 sunxi-wdt.c 的框架,利用定时器自己写一个,无意中发现内核已经有 ...

  2. MySQL 如何实现数据插入

    使用MySQL插入数据时,可以根据需求场景选择合适的插入语句,例如当数据重复时如何插入数据,如何从另一个表导入数据,如何批量插入数据等场景.本文通过给出每个使用场景下的实例来说明数据插入的实现过程和方 ...

  3. 10分钟了解MVVM,实现简易MVVM

    MVVM 是 Model-View-ViewModel 缩写,也就是把 MVC 中的 Controller 演变成 ViewModel.Model 层代表数据模型,View 代表 UI 组件,View ...

  4. vulnhub靶场之DRIFTINGBLUES: 9 (FINAL)

    准备: 攻击机:虚拟机kali.本机win10. 靶机:DriftingBlues: 9 (final),下载地址:https://download.vulnhub.com/driftingblues ...

  5. ctfshow_web入门 xss

    额,怎么说呢,对xss理解不深刻,虽然做了XSS-LAB,但是感觉不会用,看了群主的视频,知道了原因,用群主的话来说就是,X的是自己... 这个文章写得比较潦草... 准备一个带nc的工具: 无vps ...

  6. 代码随想录算法训练营day21 | leetcode ● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● ***236. 二叉树的最近公共祖先

    LeetCode 530.二叉搜索树的最小绝对差 分析1.0 二叉搜索树,中序遍历形成一个升序数组,节点差最小值一定在中序遍历两个相邻节点产生 ✡✡✡ 即 双指针思想在树遍历中的应用 class So ...

  7. CF818F - Level Generation

    题意:假设当前有 \(n\) 个点,求最多的边数,使得桥的数量 \(\ge\lceil\dfrac{m}{2}\rceil\). 我们考虑构造,首先,整张图一共只有一个双连通分量.因为我们如果有两个双 ...

  8. 基于C++的OpenGL 01 之Hello Triangle

    1. 引言 本文基于C++语言,描述OpenGL的绘制流程,这里描述的是OpenGL的核心模式(Core-profile) 本文基于Ubuntu 20.04.3 LTS系统,使用CMake构建程序,O ...

  9. LeetCode算法训练-回溯总结

    欢迎关注个人公众号:爱喝可可牛奶 LeetCode算法训练-回溯总结 适用问题 组合问题:N个数里面按一定规则找出k个数的集合 排列问题:N个数按一定规则全排列,有几种排列方式 切割问题:一个字符串按 ...

  10. (三) Mysql 之MVCC

    mvcc介绍 MVCC是数据库提供并发访问控制的一种技术.其核心理念是数据快照,不同的事务访问不同版本的数据快照,从而实现不同的事务隔离级别.虽然是说具有多个版本的数据快照,但这并不意味着数据库必须拷 ...