题意:M行N列,各元素值要么是0,要么是1,问是否可以取出其中的一些行,使取出的行组合中每列有且仅有一个1。
题目链接:http://poj.org/problem?id=3740
——>>2014.10.30 再次更新
DLX 舞蹈链模板题。。
#include <cstdio>
#include <cstring>
const int MAXM = 16 + 10;
const int MAXN = 300 + 10;
const int MAXNODE = MAXM * MAXN;
struct DLX
{
int sz;
int H[MAXM], S[MAXN];
int row[MAXNODE], col[MAXNODE];
int U[MAXNODE], D[MAXNODE], L[MAXNODE], R[MAXNODE];
void Init(int n)
{
for (int i = 0; i <= n; ++i)
{
U[i] = D[i] = i;
L[i] = i - 1;
R[i] = i + 1;
}
L[0] = n;
R[n] = 0;
sz = n + 1;
memset(S, 0, sizeof(S));
memset(H, -1, sizeof(H));
}
void Link(const int& r, const int& c)
{
row[sz] = r;
col[sz] = c;
D[sz] = D[c];
U[D[c]] = sz;
D[c] = sz;
U[sz] = c;
if (H[r] == -1)
{
H[r] = L[sz] = R[sz] = sz;
}
else
{
R[sz] = R[H[r]];
L[R[H[r]]] = sz;
R[H[r]] = sz;
L[sz] = H[r];
}
S[c]++;
sz++;
}
void Remove(const int& c)
{
L[R[c]] = L[c];
R[L[c]] = R[c];
for (int i = D[c]; i != c; 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 Restore(const int& c)
{
for (int i = U[c]; i != c; i = U[i])
{
for (int j = L[i]; j != i; j = L[j])
{
S[col[j]]++;
U[D[j]] = j;
D[U[j]] = j;
}
}
L[R[c]] = c;
R[L[c]] = c;
}
bool Dfs()
{
if (R[0] == 0)
{
return true;
}
int c = R[0];
for (int i = R[0]; i != 0; i = R[i])
{
if (S[i] < S[c])
{
c = i;
}
}
Remove(c);
for (int i = D[c]; i != c; i = D[i])
{
for (int j = R[i]; j != i; j = R[j])
{
Remove(col[j]);
}
if (Dfs()) return true;
for (int j = L[i]; j != i; j = L[j])
{
Restore(col[j]);
}
}
Restore(c);
return false;
}
bool Solve()
{
return Dfs();
}
} dlx;
int M, N;
void Read()
{
char ch;
dlx.Init(N);
for (int i = 1; i <= M; ++i)
{
for (int j = 1; j <= N; ++j)
{
getchar();
ch = getchar();
if (ch == '1')
{
dlx.Link(i, j);
}
}
}
}
void Solve()
{
dlx.Solve() ? puts("Yes, I found it") : puts("It is impossible");
}
int main()
{
while (scanf("%d%d", &M, &N) != EOF)
{
Read();
Solve();
}
return 0;
}
——>>寒假时刷这题费了超大精力,现在再刷一次,理解起来快多了。
思想:用位运算来加速。
row[i]表示第i行的二进制移位编号,第i行就将1左移i位。
col[i]表示第j列在哪些行的值为1,哪些行的值为0。
dfs(int cur, int push, int pop)表示目前考查到了第cur列,push表示已经选出来的行,pop表示已经放弃的行。
if(ok) return 0;不要没错,但是不要浪费时间,如果说有x行都满足在第cur列的要求,第一行dfs已经使ok = 1了,若不要上述的这个判断,那么剩下的x-1行都来一次dfs,费时。
if(push&pop) return 0;前面放入push的行只是满足了当时的cur列要求,并不能保证现在的cur列是否满足要求。
dfs(cur + 1, push | row[i], pop | (row[i]^col[cur]));这里没有修改push和pop的值,所以回溯也不用修改push和pop的值。
好题,好题~
#include <cstdio>
#include <cstring>
using namespace std;
const int maxr = 16;
const int maxc = 300;
int M, N, row[maxr], col[maxc], MAP[maxr][maxc];
bool ok;
void dfs(int cur, int push, int pop)
{
if(ok || (push&pop)) return;
if(cur == N)
{
ok = 1;
return;
}
for(int i = 0; i < M; i++)
{
int temp = row[i] & col[cur];
if(temp && !(row[i]&pop))
{
dfs(cur + 1, push | row[i], pop | (row[i]^col[cur]));
}
}
}
int main()
{
int i, j;
for(i = 0; i < maxr; i++) row[i] = 1<<i;
while(scanf("%d%d", &M, &N) == 2)
{
memset(col, 0, sizeof(col));
for(i = 0; i < M; i++)
for(j = 0; j < N; j++)
{
scanf("%d", &MAP[i][j]);
if(MAP[i][j]) col[j] |= row[i];
}
ok = 0;
dfs(0, 0, 0);
if(ok)
printf("Yes, I found it\n");
else
printf("It is impossible\n");
}
return 0;
}