LGP10062 [SNTS 2024] 拉丁方 学习笔记

LGP10062 [SNTS 2024] 拉丁方 学习笔记

Luogu Link

前言

本题解借鉴自这篇题解

原理相同,不过处理顺序略有不同:我是先考虑补全列数再考虑补全行数的,和题目一致,和那篇题解相反。

这题真的能当作省选 D1T3 吗?放 T2 还差不多。

说是这么说,要是放考场上你能保证切出来吗?

题意简述

定义:一个 \(n\times n\) 的矩阵是拉丁方,当且仅当每行每列都是一个 \(1\sim n\) 的排列。

现在给定一个矩阵 \(A\) 左上角的一个 \(R\times C\) 的矩阵,也就是 \(A_{i,j}(1\le i\le R,1\le j\le C)\)。问能不能把它补成一个拉丁方。多测。

\(n\le 500\)

做法解析

看到 \(n\le 500\),我感到一种 \(n^3\) 的气息——我们感性理解,这玩意的解法基本上就是某种 DP 或者某种图匹配网络流之类的东西了。另外,本题中要填的数可以当成“颜色”去考虑。

我们先从性质 B 入手——\(C=n\)。此时我们发现其必定有解法。

具体来说,因为子矩阵内合法,所以不合法的情况一定出现在子矩阵外。我们先在每列剩余的位置填上这一列缺的元素,由于子矩阵合法,所以不会出现一列上有两个相同元素的情况;然后我们再安排每一列内部顺序使其尽量合法。如果还做不到,说明肯定有不可避免的,一行里有两个相同元素 \(x\) 的情况(记这一行为 \(r_1\))——但这就说明至少有一行 \(r_2\) 不存在这种元素,并且 \(r_1,r_2\) 都在子矩阵外。那么我们从 \(r_1\) 换一个 \(x\)\(r_2\) 就行,和“肯定不可避免”矛盾。

上文是对其必然有解的感性刻画;有没有更本质的刻画证明呢?有的兄弟有的。

我们既然想要严格刻画,再加上这个复杂度,我们大胆往图匹配上面靠。

我们建立一张二分图,左右各 \(n\) 个点,如果第 \(i\) 列还缺第 \(j\) 种“颜色”的话就连一条 \((i,j)\) 的边。这样的话,我们发现其中一组完美匹配就等价于一行的合法填数方案。

二分图中,一组完美匹配是指 \(n\) 条两两不共端点的边所组成的边集。此处完美匹配之所以有这样的意义:左部没有端点重合,意味着一个位置只能填一个数,右部没有端点重合,意味着一个颜色在一行内只能出现一次。

pEdIvyn.png

如果我们能拆出来 \(n-R\) 个完美匹配,那就说明有解了。而判定并求解能否拆出 \(k\) 组完美匹配的过程,就等价于用 \(k\) 种颜色进行二分图边染色的过程。不会二分图边染色的可参考我的CFP600题解。而根据二分图边染色所需最小颜色数量等于图中最大度数的结论,这样一张图里面所有点的度数都是 \(n-R\),所以必然有解。

那么对于更一般的情况呢?我们首先考虑把 \(R\times C\) 补成 \(R\times N\)。这一步和上一步的处理是类似的,见图和代码。需要注意的是,此时图中最大度数 \(mxd\) 就不一定等于 \(N-C\) 了——如果大于的话,那说明无解,因为在 \(N-C\) 列里面填入 \(mxd\) 个颜色 \(x\) 是做不到的。

pEdIxLq.md.png

那这题就做完了!时间复杂度 \(O(Tn^3)\)\(n\le 500\) 可过。细节见代码。

代码实现

封装一个二分图染色器有助于写出优异的代码。

有一点细节,想清楚再写。

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=505;
int N,R,C,A[MaxN][MaxN];
struct Painter{
    int tov[MaxN<<1][MaxN];
    void init(int l,int d){
        for(int i=1;i<=l;i++){
            fill(tov[i]+1,tov[i]+d+1,0);
        }
    }
    void add(int u,int v){
        int cc1=1,cc2=1;
        while(tov[u][cc1])cc1++;
        while(tov[v][cc2])cc2++;
        tov[u][cc1]=v,tov[v][cc2]=u;
        if(cc1==cc2)return;
        for(int tc=cc2,w=v,cx=cc1^cc2;w;){
            swap(tov[w][cc1],tov[w][cc2]);
            w=tov[w][tc],tc^=cx;
        }
    }
}P;
int tmp[MaxN];
bool solve1(){
    P.init(R+N,N-C);fill(tmp+1,tmp+N+1,0);
    for(int i=1;i<=R;i++)for(int j=1;j<=C;j++)tmp[A[i][j]]++;
    for(int i=1;i<=N;i++)if(R-tmp[i]>N-C)return 0;
    for(int i=1;i<=R;i++){
        fill(tmp+1,tmp+N+1,0);
        for(int j=1;j<=C;j++)tmp[A[i][j]]++;
        for(int j=1;j<=N;j++)if(!tmp[j])P.add(i,j+R);
    }
    for(int i=1;i<=R;i++){
        for(int j=1;j<=N-C;j++){
            if(P.tov[i][j])A[i][j+C]=P.tov[i][j]-R;
        }
    }
    return 1;
}
void solve2(){
    P.init(N*2,N);
    for(int i=1;i<=N;i++){
        fill(tmp+1,tmp+N+1,0);
        for(int j=1;j<=R;j++)tmp[A[j][i]]++;
        for(int j=1;j<=N;j++)if(!tmp[j])P.add(i,j+N);
    }
    for(int i=1;i<=N;i++){
        for(int j=1;j<=N;j++){
            if(P.tov[i][j])A[j+R][i]=P.tov[i][j]-N;
        }
    }
}
void mian(){
    readi(N),readi(R),readi(C);
    for(int i=1;i<=R;i++)for(int j=1;j<=C;j++)readi(A[i][j]);
    if(C<N&&!solve1()){puts("No");return;};solve2(),puts("Yes");
    for(int i=1;i<=N;i++,puts(""))for(int j=1;j<=N;j++)writi(A[i][j]),putchar(' ');
}
int Tcn;
int main(){
    readi(Tcn);
    while(Tcn--)mian();
    return 0;
}

反思总结

填色可行性类问题,想想图匹配!

posted @ 2025-03-18 20:13  矞龙OrinLoong  阅读(55)  评论(1)    收藏  举报