Dancing Links X 舞蹈鏈。
實質為用迴圈十字鏈在圖上將所有“1”的位置鏈起來
構造較為巧妙,且極易理解,本題為 DLX 模板(精確覆蓋問題)
DLX 演算法的題目做法一般為將所求方案轉化為行號,將限制條件轉化為列號
然後 dfs 暴力列舉,用迴圈十字鏈最佳化
/*
Finished at 12:14 on 2024.4.5
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 510, M = 250510;
int n, m;
int a[N][N];
int row[M], col[M];
//row為每個點所在行號,col為每個點所在列號
int cnt, s[N], h[N];
//cnt為給每個鏈上的點的編號
//s表示某一列上所建連結串列點個數
//h為每一行的列表頭
int u[M], d[M], l[M], r[M];
//u, d, l, r分別表示某個連結串列點上下左右所連的
int res[N];
//所選行號
void init() //初始化第0行的連結串列頭
{
for (int i = 0; i <= m; i ++ )
u[i] = d[i] = i, l[i] = i - 1, r[i] = i + 1; //初始化左右,上下還沒點,所以指向自己
l[0] = m, r[m] = 0, cnt = m; //處理剩下的0,m點
}
void link(int x, int y)
{
s[y] ++ ;
cnt ++ ;
row[cnt] = x, col[cnt] = y;
u[cnt] = y;
d[cnt] = d[y]; //可類比連結串列,正常加即可
u[d[y]] = cnt;
d[y] = cnt;
if (!h[x]) h[x] = l[cnt] = r[cnt] = cnt; //本行無連結串列點,則加進去
else
{
l[cnt] = l[h[x]];
r[cnt] = h[x]; //正常雙向連結串列加
r[l[h[x]]] = cnt;
l[h[x]] = cnt;
}
}
void remove(int x)
{
r[l[x]] = r[x], l[r[x]] = l[x];
for (int i = d[x]; i != x; i = d[i]) //向下,向右刪除每個點
for (int j = r[i]; j != i; j = r[j])
u[d[j]] = u[j], d[u[j]] = d[j], s[col[j]] -- ;
}
void resume(int x)
{
r[l[x]] = x, l[r[x]] = x;
for (int i = u[x]; i != x; i = u[i]) //向上,向左恢復每個點
for (int j = l[i]; j != i; j = l[j])
u[d[j]] = j, d[u[j]] = j, s[col[j]] ++ ;
}
bool dance(int depth)
{
if (r[0] == 0)
{
for (int i = 0; i < depth; i ++ ) cout << res[i] << ' ';
cout << '\n'; //第0行刪完了
return true;
}
int y = r[0];
for (int i = r[0]; i; i = r[i]) //優先找1少的
if (s[y] > s[i]) y = i;
remove(y);
for (int i = d[y]; i != y; i = d[i])
{
res[depth] = row[i];
for (int j = r[i]; j != i; j = r[j]) remove(col[j]);
if (dance(depth + 1)) return true; //暴力列舉
for (int j = l[i]; j != i; j = l[j]) resume(col[j]);
}
resume(y);
return false;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
cin >> a[i][j];
init();
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
if (a[i][j]) link(i, j); //1位置加點
if (!dance(0)) cout << "No Solution!\n";
return 0;
}