Easy Finding
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 18790 | Accepted: 5184 |
Description
Given a M×N matrix A. Aij ∈ {0, 1} (0 ≤ i < M, 0 ≤ j < N), could you find some rows that let every cloumn contains and only contains one 1.
Input
There are multiple cases ended by EOF. Test case up to 500.The first line of input is M, N (M ≤ 16, N ≤ 300). The next M lines every line contains N integers separated by space.
Output
For each test case, if you could find it output "Yes, I found it", otherwise output "It is impossible" per line.
Sample Input
3 3 0 1 0 0 0 1 1 0 0 4 4 0 0 0 1 1 0 0 0 1 1 0 1 0 1 0 0
Sample Output
Yes, I found it It is impossible
Source
POJ Monthly Contest - 2009.08.23, MasterLuo
1、从矩阵中选择一行
2、根据定义,标示矩阵中其他行的元素
3、删除相关行和列的元素,得到新矩阵
4、如果新矩阵是空矩阵,并且之前的一行都是1,那么求解结束,跳转到6;新矩阵不是空矩阵,继续求解,跳转到1;新矩阵是空矩阵,之前的一行中有0,跳转到5
5、说明之前的选择有误,回溯到之前的一个矩阵,跳转到1;如果没有矩阵可以回溯,说明该问题无解,跳转到7
6、求解结束,把结果输出
7、求解结束,输出无解消息
Dancing Links用的数据结构是交叉十字循环双向链
而Dancing Links中的每个元素不仅是横向循环双向链中的一份子,又是纵向循环双向链的一份子。
因为精确覆盖问题的矩阵往往是稀疏矩阵(矩阵中,0的个数多于1),Dancing Links仅仅记录矩阵中值是1的元素。
#include <cstdio> #include <cstring> const int MAXR = 20; const int MAXC = 310; const int MAXN = MAXR * MAXC + MAXC; const int INF = MAXR * 10; int n, m; int L[MAXN], R[MAXN], U[MAXN], D[MAXN]; int C[MAXN], O[MAXN], S[MAXN], H[MAXR]; int nodeNumber; void init() { for(int i=0;i<=m;++i) { L[i] = i - 1; R[i] = i + 1; U[i] = i; D[i] = i; C[i] = i; O[i] = 0; S[i] = 0; } L[0] = m; R[m] = 0; nodeNumber = m + 1; memset(H, 0, sizeof(H)); } void insert(int i, int j) { if(H[i]) //判断这一行中有没有节点 { L[nodeNumber] = L[H[i]]; //如果有节点了,就添加一个节点,并把左指针指向第一个节点的未被更新的左指针,也就是新节点的左指针 R[nodeNumber] = H[i]; //右指针指向该行第一个节点 L[R[nodeNumber]] = nodeNumber; //更新第一个节点的左指针 R[L[nodeNumber]] = nodeNumber; //更新前一个节点的右指针 } else { L[nodeNumber] = nodeNumber; //如果没有节点就添加一个节点,并把左右指针指向自己 R[nodeNumber] = nodeNumber; H[i] = nodeNumber; //标记为该行第一个节点 } U[nodeNumber] = U[j]; //节点的上指针指向上面一个节点 D[nodeNumber] = j; //节点的下指针指向对应的列表头 U[D[nodeNumber]] = nodeNumber; //更新列表头的上指针指向当前节点 D[U[nodeNumber]] = nodeNumber; //更新上一个节点的下指针指向当前节点 C[nodeNumber] = j; //记录列号 O[nodeNumber] = i; //记录行号 ++ S[j]; //S当中记录着每列节点的个数 ++ nodeNumber; //新建一个节点 } void remove(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[C[j]]; //把相应列上对应的节点数也减少1个 } } } void resume(int c) { for(int i=U[c];i!=c;i=U[i]) //从该列最后一个节点往上遍历,不遍历列表头节点 { for(int j=L[i];j!=i;j=L[j]) //从该行最后一个节点往左遍历,不遍历第一个节点 { ++ S[C[j]]; //列上面恢复一个节点,节点数也+1 D[U[j]] = j; //恢复行 U[D[j]] = j; } } R[L[c]] = c; //最后恢复列 L[R[c]] = c; } bool dfs(int k) { if(!R[0]) //如果列表头上第一个节点的右指针为0,即所有列都被删除,则搜索完成 { return true; } //因为要输出最优秀(最少的行)的答案,每次都要优先搜索列节点最少的列 int count = INF, c; for(int i=R[0];i;i=R[i]) //从第一个列开始,直到右指针指向列头,即逐列遍历 { if(S[i] < count) //找到节点最少的列 { count = S[i]; //count里面放最少的节点数 c = i; //把该列做标记 if(1 == count) //该列节点,为最少允许的情况直接算是找到了,跳出 { break; } } } remove(c); //把该列和列上符合要求的行删除 for(int i=D[c];i!=c;i=D[i]) //在被删除的列上,往下遍历 { for(int j=R[i];j!=i;j=R[j]) //对应的行上往右遍历 { remove(C[j]); //如果行上有符合要求的列,删了 } if(dfs(k+1)) //递归层数+1,深度搜索 { return true; } for(int j=L[i];j!=i;j=L[j]) //从该行最后一个节点往左遍历,第一个节点不遍历 { resume(C[j]); //恢复之前删除的*行* } } resume(c); //递归跳出,恢复之前删除的列 return false; } int main() { int t; while(~scanf("%d%d",&n,&m)) { init(); /* printf("L\tR\tU\tD\tC\tO\n"); for(int i=0;i<=m;i++) { printf("%d\t",L[i]); printf("%d\t",R[i]); printf("%d\t",U[i]); printf("%d\t",D[i]); printf("%d\t",C[i]); printf("%d\t\n",O[i]); } */ for(int i=1;i<=n;++i) { for(int j=1;j<=m;++j) { scanf("%d", &t); if(t) { insert(i, j); //建立抽象十字链表 } } } bool flag = true; for(int i=1;i<=m;++i) { if(S[i] == 0) //如果有一列没有一个节点,直接失败 { flag = false; break; } } if(flag && dfs(0)) //进入深度搜索 { printf("Yes, I found it\n"); } else { printf("It is impossible\n"); } } return 0; } /* 6 7 0 0 1 0 1 1 0 1 0 0 1 0 0 1 0 1 1 0 0 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 1 0 1 */
原文地址:https://www.cnblogs.com/caiyishuai/p/9019036.html