题意: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; }