Many problems (such as solving Sudoku, etc.) can be transformed into solving precise coverage problems. It would be great if there is a convenient code to solve this problem.
So the great predecessors invented the X algorithm, using the dancing link to solve this problem.
https://blog.csdn.net/whereisherofrom/article/details/79220897
https://www.cnblogs.com/grenet/p/3145800.html
These two blogs explain the algorithm ideas of X algorithm very well.
It's a pity that the two pieces of code, one is a language I don't know, and the other is C++ code but it is more engineering. It feels not very conducive to quickly understand and implement this algorithm, so here is a simple implementation code in C++.
The core idea of the X algorithm is to use dancing link for brute force search and then backtracking.
The title corresponds to Luogu P4929
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 555;
struct node{
int x, y;
int l, r, u, d;
}e[maxn*maxn];
int head, col[maxn], row[maxn], tot = 0, sz[maxn];
int a[maxn][maxn];
void add(int i, int j){
++tot;//e[tot]代表新结点
sz[j]++;//第j列的点数+1
e[tot] = (node){
i, j, e[row[i]].l, row[i], e[col[j]].u, col[j]};//四个方向
e[e[tot].u].d = tot;//tot上面的点 的 下面的点 变成tot
e[e[tot].d].u = tot;//tot下面的点 的 上面的点 变成tot
e[e[tot].l].r = tot;//tot左面的点 的 右面的点 变成tot
e[e[tot].r].l = tot;//tot右面的点 的 左面的点 变成tot
return;
}
void init(int n, int m){
tot = 0;
head = ++tot;
e[head] = (node){
0, 0, head, head, head, head};
for(int i = 1; i <= n; ++i){
row[i] = ++tot;
e[tot] = (node){
i, 0, tot, tot, e[head].u,head};
e[e[tot].u].d = tot;
e[e[tot].d].u = tot;
}
for(int i = 1; i <= m; ++i){
col[i] = ++tot;
e[tot] = (node){
0,i,e[head].l, head, tot, tot};
e[e[tot].l].r = tot;
e[e[tot].r].l = tot;
}
fors(i, 1, n+1) fors(j, 1, m+1) if(a[i][j]) add(i, j);
}
int n, m;
void del_lr(int id){
e[e[id].r].l = e[id].l;
e[e[id].l].r = e[id].r;
}
void del_ud(int id){
e[e[id].u].d = e[id].d;
e[e[id].d].u = e[id].u;
}
void re_lr(int id){
e[e[id].r].l = e[e[id].l].r = id;
}
void re_ud(int id){
e[e[id].u].d = e[e[id].d].u = id;
}
void cover(int y){
//删除第y列
del_lr(col[y]);
for(int cy = e[col[y]].d; cy != col[y]; cy = e[cy].d){
//枚举这个列上的1
for(int cx = e[cy].l; cx != cy; cx = e[cx].l){
//枚举行上的1,删除上下关系
del_ud(cx);
sz[e[cx].y]--;//这一列1的个数减1
}
}
}
void re(int y){
//恢复第y列
re_lr(col[y]);
for(int cy = e[col[y]].d; cy != col[y]; cy = e[cy].d){
for(int cx = e[cy].l; cx != cy; cx = e[cx].l){
re_ud(cx);
sz[e[cx].y]++;
}
}
}
stack<int> ans;
bool dance(){
if(e[head].l == head){
//空矩阵
return true;
}
int mi = 0x3f3f3f3f, cid = -1;
for(int i = e[head].r; i != head; i = e[i].r){
if(mi > sz[e[i].y]) mi = sz[e[i].y], cid = e[i].y;
}//找一个列上的1个数最少的列
cover(cid);
for(int cy = e[col[cid]].d; cy != col[cid]; cy = e[cy].d){
//枚举选哪行
for(int cx = e[cy].l; cx != cy; cx = e[cx].l){
if(e[cx].y == 0) continue;//是辅助数组row对应的结点,则不删除列
cover(e[cx].y);
}
ans.push(e[cy].x);//行号压入栈
if(dance()) return true;//子问题得到解决,返回true
ans.pop();//这个行号不行,弹出
for(int cx = e[cy].l; cx != cy; cx = e[cx].l){
//恢复刚刚被删除的列
if(e[cx].y == 0) continue;
re(e[cx].y);
}
}
re(cid);
return false;
}
int main()
{
cin>>n>>m;
fors(i, 1, n+1) fors(j, 1, m+1) scanf("%d", &a[i][j]);
init(n, m);
if(dance()){
while(ans.size()) printf("%d ", ans.top()), ans.pop();
}else{
cout<<"No Solution!"<<endl;
}
}