题解:P11047 [蓝桥杯 2024 省 Java B] LITS 游戏
前置知识:精确覆盖问题
精确覆盖问题简单来说就是,给定
舞蹈链是一种解决精确覆盖问题的实现方法,在实际应用中,我们通常把行当做选择,列当做限制。也就是说,我们有
本题思路
首先统计出值为 1 的格子数量
然后枚举每个值为 1 的格子,查看如果以当前格子为起点,是否能摆出某个图形,如果可以,就把这个图案涉及到的几个格子都插入,再插入这个图形的列。否则,只插入当前格子自己的列。
建模完成后,我们使用舞蹈链模板求解即可。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 55, M = 500000;
struct Pos {
int x, y;
};
char arr[N][N];
int idle = 0;
int L[M], R[M], U[M], D[M], head[M];
int cnt[N * N];
Pos pos[M];
int n, m;
int dir[4][4][4][2] = {
{ //图案
{ //旋转
{ 0, 0 }, { -1, 0 }, { -2, 0 }, { 0, 1 } //坐标偏移
},
{
{ 0, 0 }, { 0, 1 }, { 0, 2 }, { 1, 0 }
},
{
{ 0, 0 }, { 0, -1 }, { 1, 0 }, { 2, 0 }
},
{
{ 0, 0 }, { -1, 0}, { 0, -1 }, { 0, -2 }
}
},
{
{
{ 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 }
},
{
{ 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }
},
{
{ 0, 0 }, { 0, 1 }, { 0, 2 }, { 0, 3 }
},
{
{ 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }
}
},
{
{
{ 0, 0 }, { 0, 1 }, { 0, -1 }, { 1, 0 }
},
{
{ 0, 0 }, { 0, -1 }, { 1, 0 }, { -1, 0 }
},
{
{ 0, 0 }, { -1, 0 }, { 0, -1 }, { 0, 1 }
},
{
{ 0, 0 }, { 0, 1 }, { -1, 0 }, { 1, 0 }
}
},
{
{
{ 0, 0 }, { 0, 1 }, { -1, 1 }, { -1, 2 }
},
{
{ 0, 0 }, { 1, 0 }, { 1, 1 }, { 2, 1 }
},
{
{ 0, 0 }, { 0, -1 }, { 1, -1 }, { 1, -2 }
},
{
{ 0, 0 }, { -1, 0 }, { -1, -1 }, { -2, -1 }
}
}
};
void insert(int row, int col) {
idle++;
cnt[col]++;
pos[idle] = { row, col };
U[idle] = U[col];
D[U[col]] = idle;
U[col] = idle;
D[idle] = col;
if (!head[row]) head[row] = L[idle] = R[idle] = idle;
else {
L[idle] = L[head[row]];
R[L[head[row]]] = idle;
L[head[row]] = idle;
R[idle] = head[row];
}
}
int ind[N][N] = { 0 }; //为每个 '1' 分配列
void build() {
memset(head, 0, sizeof head);
memset(cnt, 0, sizeof cnt);
memset(ind, 0, sizeof ind);
m = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (arr[i][j] == '1')
m++, ind[i][j] = m;
m += 4; //为四个图形分配列
for (int i = 0; i <= m; i++) {
L[i] = i - 1, R[i] = i + 1;
U[i] = D[i] = i;
pos[i] = { 0, i };
}
idle = m;
L[0] = m, R[m] = 0;
int r = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
if (arr[i][j] == '1') {
for (int t = 0; t < 4; t++) //遍历每个图案
for (int d = 0; d < 4; d++) { //遍历每个角度
bool f = true;
for (int c = 0; c < 4; c++) { //查看每个偏移
int dx = i + dir[t][d][c][0], dy = j + dir[t][d][c][1];
if (dx < 1 || dy < 1 || dx > n || dy > n || arr[dx][dy] != '1') {
f = false;
break;
}
}
if (f) {
r++;
insert(r, m - 3 + t);
for (int c = 0; c < 4; c++) {
int dx = i + dir[t][d][c][0], dy = j + dir[t][d][c][1];
insert(r, ind[dx][dy]);
}
}
}
r++;
insert(r, ind[i][j]);
}
}
//冻结第 i 列及其关联行
void freeze(int col) {
L[R[col]] = L[col], R[L[col]] = R[col];
for (int i = D[col]; i != col; i = D[i])
for (int j = R[i]; j != i; j = R[j])
cnt[pos[j].y] --, U[D[j]] = U[j], D[U[j]] = D[j];
}
//激活第 i 列及其关联行
void active(int col) {
L[R[col]] = R[L[col]] = col;
for (int i = U[col]; i != col; i = U[i])
for (int j = L[i]; j != i; j = L[j])
cnt[pos[j].y] ++, U[D[j]] = j, D[U[j]] = j;
}
bool dance() {
if (!R[0]) return true;
int s = R[0];
for (int i = R[s]; i; i = R[i])
if (cnt[i] < cnt[s])
s = i;
freeze(s);
for (int i = D[s]; i != s; i = D[i]) {
for (int j = R[i]; j != i; j = R[j])
freeze(pos[j].y);
if (dance()) return true;
for (int j = L[i]; j != i; j = L[j])
active(pos[j].y);
}
active(s);
return false;
}
int main() {
int _;
scanf("%d", &_);
while (_--) {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
scanf(" %c", &arr[i][j]);
build();
if (dance()) puts("Yes");
else puts("No");
}
return 0;
}