题意

给出一个 \(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. 文献阅读01:由I类HLA转录缺失导致的联合免疫治疗的获得性癌症耐药性

    背景 Merkel cell carcinoma:梅克尔细胞癌又名皮肤小梁状癌.原发性皮肤神经内分泌癌.皮肤原发性小细胞癌及皮肤APUD瘤. HLA:MHC基因产物在不同细胞表面表达,通常称之为MHC ...

  2. 编程哲学之 C# 篇:004——安装 Visual Studio

    工欲善其事必先利其器,本章介绍安装Visual Studio这个号称宇宙最强IDE(Integrated Development Environment[集成开发环境]). 安装 Visual Stu ...

  3. 线程基础知识02-CompletableFuture

    1 简介 Futrue可以监视目标线程调用call的情况,当你调用Future的get()方法以获得结果时,调用方的线程就被阻塞,直到目标线程的call方法结束并返回结果. 线程的实现方式有几种方式, ...

  4. Debug时使用热部署修改代码

    今晚Debug的时候,一些语句怎么也不能debug,F8步过以下跳好多行,看起来很烦人.原来是有些行无法进入debug断点. 是因为Debug之前Tomcat已经编译Class文件,当插入注释/修改代 ...

  5. ubuntu 备份系统

    1.安装Systemback: sudo add-apt-repository ppa:nemh/systemback sudo apt-get update sudo apt-get install ...

  6. flutter系列之:在flutter中使用导航Navigator

    目录 简介 flutter中的Navigator Navigator的使用 总结 简介 一个APP如果没有页面跳转那么是没有灵魂的,页面跳转的一个常用说法就是Navigator,flutter作为一个 ...

  7. TCP/IP 协议(10):TCP 协议一百问

    TCP/IP 协议(10):TCP 协议一百问 杨领well 的 TCP/IP 协议专栏 TCP 协议部分一直没有更新,是因为我不确定到底应该怎么来介绍 TCP 协议才能干货满满.最后我决定以 Q&a ...

  8. 【KAWAKO】iphone13pro开箱流程

    目录 全程录像 检查包装盒 检查包装盒内物品 检查各种码 拆封 激活 激活之后 检查屏幕 检查其它功能 贴膜(选) References 全程录像 如果你觉得你所购买的平台 (比如某ABB格式名字的平 ...

  9. Java 文本检索神器 "正则表达式"

    Java 文本检索神器 "正则表达式" 每博一文案 在我们短促而又漫长的一生中,我们在苦苦地寻找人生的幸福,可幸福往往又与我们失之交臂, 当我们为此而耗尽宝贵的.青春年华,皱纹也悄 ...

  10. Angular UI库

    1.angular 使用 bootstrap 安装bootstrap npm install bootstrap --save 安装bootstrap-icons npm i bootstrap-ic ...