LGP10062 [SNTS 2024] 拉丁方 学习笔记
LGP10062 [SNTS 2024] 拉丁方 学习笔记
前言
本题解借鉴自这篇题解。
原理相同,不过处理顺序略有不同:我是先考虑补全列数再考虑补全行数的,和题目一致,和那篇题解相反。
这题真的能当作省选 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\) 条两两不共端点的边所组成的边集。此处完美匹配之所以有这样的意义:左部没有端点重合,意味着一个位置只能填一个数,右部没有端点重合,意味着一个颜色在一行内只能出现一次。

如果我们能拆出来 \(n-R\) 个完美匹配,那就说明有解了。而判定并求解能否拆出 \(k\) 组完美匹配的过程,就等价于用 \(k\) 种颜色进行二分图边染色的过程。不会二分图边染色的可参考我的CFP600题解。而根据二分图边染色所需最小颜色数量等于图中最大度数的结论,这样一张图里面所有点的度数都是 \(n-R\),所以必然有解。
那么对于更一般的情况呢?我们首先考虑把 \(R\times C\) 补成 \(R\times N\)。这一步和上一步的处理是类似的,见图和代码。需要注意的是,此时图中最大度数 \(mxd\) 就不一定等于 \(N-C\) 了——如果大于的话,那说明无解,因为在 \(N-C\) 列里面填入 \(mxd\) 个颜色 \(x\) 是做不到的。

那这题就做完了!时间复杂度 \(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;
}
反思总结
填色可行性类问题,想想图匹配!
浙公网安备 33010602011771号