///定义(ctrl+c):
/*
(1)历史:
X算法是高德纳提出的解决精确覆盖问题的算法,而dancing links X算法则是DonKnuth(《计算机程序设计艺术》
的作者)提出的对X算法的一种高效实现,这种实现建立在如上所说的矩阵表示法上。
(2)算法思想:
由如上精确覆盖问题的矩阵表示法中,我们知道dancing links x 是用来求解一个 01矩阵中选取哪几行可以使得
这几行每一列都有且仅有一个1(就是每个元素在这几个子集里有且仅有出现过一次)。先不管他的实际意义,
我们需要做的就是在一个01矩阵的选取某几行使之符合上述条件。我们很容易就想到枚举,然后判断符不符合条件,
但是这个做法实在是太消耗时间。dacing links x就是一个高效的求解该类问题的算法,而这种算法,基于交叉十字
循环双向链的数据结构。
(3)精确覆盖问题的定义:
给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
(4)重复覆盖问题:
即选取一个01矩阵中的几行,使这几行组成的新矩阵的每一列至少有一个1。
该问题在精确覆盖问题上减少了一个约束条件
(5)应用:
解决精确覆盖问题和重复覆盖问题
*/
///代码:
/*
**name:舞蹈连-精确覆盖问题
**function:给定一个由0-1组成的矩阵,是否能找到一个行的集合,使得集合中每一列都恰好包含一个1
**输入参数:包含1的位置(i,j)
**输出参数:行集合的信息
*/
#include <iostream>
#include <cstdio>
using namespace std;
const int maxnode = 100010;
const int maxm = 1010;
const int maxn = 1010;
struct DLX{
int n,m,len;
int U[maxnode],D[maxnode],R[maxnode],L[maxnode],Row[maxnode],Col[maxnode];
int H[maxn];//行头结点
int S[maxm];//每列有多少个结点
int ansd,ans[maxn];//如果有答案,则选了ansd行,具体是哪几行放在ans[]数组里面,ans[0~ansd-1]
void init(int _n,int _m){
n = _n;m = _m;
for(int i = 0; i <= m; i++){
S[i] = 0;
U[i] = D[i] = i;//初始状态时,上下都指向自己
L[i] = i-1;
R[i] = i+1;
}
R[m] = 0,L[0] = m;
len = m;//编号,每列都有一个头结点,编号1~m
for(int i = 1; i <= n; i++)
H[i] = -1;//每一行的头结点
}
void link(int r,int c){//第r行,第c列
++S[Col[++len]=c];//第len个节点所在的列为c,当前列的结点数++
Row[len] = r;//第len个结点行位置为r
D[len] = D[c];
U[D[c]] = len;
U[len] = c;
D[c] = len;
if(H[r] < 0)
H[r] = L[len] = R[len] = len;
else{
R[len] = R[H[r]];
L[R[H[r]]] = len;
L[len] = H[r];
R[H[r]] = len;
}
}
void del(int c){//删除结点c,以及c上下结点所在的行
//每次调用这个函数,都是从列头节点开始向下删除,这里c也可以理解为第c列
//因为第c列的列头节点编号为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 resume(int c){//回复结点c,以及c上下节点所在的行(同上,也可以理解为从第c列的头节点开始恢复
for(int i = U[c]; i != c; i = U[i]){
for(int j = L[i]; j != i; j = L[j]){
++S[Col[U[D[j]]=D[U[j]]=j]];
}
}
L[R[c]] = R[L[c]] = c;
}
bool dance(int d){//递归深度
if(R[0] == 0){
ansd = d;
return true;
}
int c = R[0];
for(int i = R[0]; i != 0; i = R[i]){
if(S[i] < S[c])
c = i;
}
del(c);//找到结点数最少的列,当前元素不是原图上0,1的节点,而是列头节点
for(int i = D[c]; i != c; i = D[i]){
ans[d] = Row[i];//列头节点下面的一个节点
for(int j = R[i]; j != i; j = R[j])
del(Col[j]);
if(dance(d+1))//找到,返回
return true;
for(int j = L[i]; j != i; j = L[j])
resume(Col[j]);
}
resume(c);
return false;
}
};
DLX head;
int main(){
int n,m,k;
while(~scanf("%d%d%d",&n,&m,&k)){
head.init(n,m);
for(int i = 1,x; i <= n; i++){
for(int j = 1;j<=m;j++){
scanf("%d",&x);
if(!x)head.link(i,j);
}
}
if(!head.dance(0))
printf("NO\n");
else{
printf("%d\n",head.ansd);
for(int i = 0; i < head.ansd; i++)
printf(" %d",head.ans[i]);
printf("\n");
}
}
return 0;
}
/*
**name: 舞蹈连-重复覆盖问题
**function: 即选取一个01矩阵中的几行,使这几行组成的新矩阵的每一列至少有一个1。
**输入参数:包含1的位置(i,j)
**输出参数:至少所消耗的行数,如果返回无穷大则表明不存在
*/
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
struct DLX
{
int n,m,len,ansd;
int S[400],U[200000],D[200000],L[200000],R[200000],Row[200000],Col[200000];
int H[400];
void init(int _n,int _m)
{
n=_n;
m=_m;
len=m;
ansd=0x3f3f3f3f;
for(int i=0; i<=m; i++)
{
S[i]=0;
U[i]=D[i]=i;
R[i]=i+1;
L[i]=i-1;
Row[i]=0;
Col[i]=i;
}
R[m]=0;
L[0]=m;
for(int i=1; i<=n; i++)
H[i]=-1;
}
void link(int r,int c)
{
++len;
++S[c];
Row[len]=r;
Col[len]=c;
D[len]=D[c];
U[D[c]]=len;
U[len]=c;
D[c]=len;
if(H[r]==-1)
{
H[r]=R[len]=L[len]=len;
}
else
{
R[len]=R[H[r]];
L[R[H[r]]]=len;
L[len]=H[r];
R[H[r]]=len;
}
}
int f()
{
int ans=0;
int V[400];
memset(V,0,sizeof(V));
for(int i=R[0]; i!=0; i=R[i])
{
V[i]=true;
}
for(int i=R[0]; i!=0; i=R[i])
{
if(V[i])
{
ans++;
V[i]=false;
for(int j=D[i]; j!=i; j=D[j])
{
for(int k=R[j]; k!=j; k=R[k])
{
V[Col[k]]=false;
}
}
}
}
return ans;
}
void del(int c)
{
for(int i=D[c]; i!=c; i=D[i])
{
R[L[i]]=R[i];
L[R[i]]=L[i];
}
}
void resume(int c)
{
for(int i=U[c]; i!=c; i=U[i])
{
L[R[i]]=i;
R[L[i]]=i;
}
}
void Dance(int d)
{
if(d>=ansd)return ;
if(d+f()>=ansd)return ;
if(R[0]==0)
{
ansd=min(ansd,d);
return ;
}
int c=R[0];
for(int i=R[0]; i!=0; i=R[i])
{
if(S[i]<S[c])
c=i;
}
for(int i=D[c]; i!=c; i=D[i])
{
del(i);
for(int j=R[i]; j!=i; j=R[j])del(j);
Dance(d+1);
for(int j=L[i]; j!=i; j=L[j])resume(j);
resume(i);
}
return ;
}
};
DLX head;
int main(){
int n,m,k,x;
while(~scanf("%d%d%d",&n,&m,&k)){
head.init(n,m);
for(int i = 1;i<=n;i++){
for(int j = 1;j<=m;j++){
scanf("%d",&x);
if(!x)head.link(i,j);
}
}
head.Dance(0);
if(head.ansd<=k&&head.ansd!=0x3f3f3f3f)printf("yes\n");
else printf("no\n");
//printf("%d\n",head.ansd);
}
return 0;
}
/*
**name:舞蹈连-精确覆盖问题
**function:求解数独
**输入参数:数独盘的字符串
**输出参数:完整的数独盘字符串
*/
#include <cstring>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=1000,maxm=400,maxnode=400000;
struct DLX
{
int n,m,len;
int U[maxnode],D[maxnode],R[maxnode],L[maxnode],Row[maxnode],Col[maxnode];
int H[maxn];///行头结点
int S[maxm];///每列有多少个结点
int ansd,ans[maxn];
void init(int _n,int _m)
{
n=_n;
m=_m;
for(int i=0; i<=m; i++)
{
S[i]=0;
U[i]=D[i]=i;
L[i]=i-1;
R[i]=i+1;
}
R[m]=0,L[0]=m;
len=m;
for(int i=1; i<=n; i++)
{
//cout<<"**"<<endl;
H[i]=-1;
}
}
void link(int r,int c)
{
++S[Col[++len]=c];
Row[len]=r;
D[len]=D[c];
U[D[c]]=len;
U[len]=c;
D[c]=len;
if(H[r]<0)
H[r]=L[len]=R[len]=len;
else
{
R[len]=R[H[r]];
L[R[H[r]]]=len;
L[len]=H[r];
R[H[r]]=len;
}
}
void del(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 resume(int c)
{
for(int i=U[c]; i!=c; i=U[i])
{
for(int j=L[i]; j!=i; j=L[j])
{
++S[Col[U[D[j]]=D[U[j]]=j]];
}
}
L[R[c]]=R[L[c]]=c;
}
bool dance(int d)
{
//cout<<d<<endl;
if(R[0]==0)
{
ansd=d;
return true;
}
int c=R[0];
///找一个1少的列,相当于剪枝了
for(int i=R[0]; i!=0; i=R[i])
{
//cout<<S[i]<<endl;
if(S[i]<S[c])
{
c=i;
}
}
del(c);
//cout<<S[1]<<endl;
//cout<<"del()"<<c<<endl;
//cout<<D[c]<<" "<<endl;
for(int i=D[c]; i!=c; i=D[i])
{
//cout<<i<<endl;
ans[d]=Row[i];
for(int j=R[i]; j!=i; j=R[j])
{
del(Col[j]);
}
if(dance(d+1))return true;
for(int j=L[i]; j!=i; j=L[j])
{
resume(Col[j]);
}
}
resume(c);
return false;
}
};
struct node
{
int pos;
int data;
int lie;
} q[2000];
DLX head;
int main()
{
char s[100];
while(scanf("%s",s))
{
if(strcmp(s,"end")==0)break;
int len=strlen(s);
head.init(1000,324);
int row=0;
for(int i=0; i<len; i++)
{
int x=i/9,y=i%9;
if(s[i]=='.')
{
for(int j=1; j<=9; j++)
{
//x行y列填了一个数字
head.link(++row,x*9+(y+1));
//x行填了一个数字j
head.link(row,x*9+j+81);
//y列填了一个数字j
head.link(row,y*9+j+162);
//g宫填了一个数字j
int xx=x/3*3,yy=y/3*3;
int g=xx/3*3+yy/3+1;
head.link(row,(g-1)*9+j+243);
q[row].pos=i;
q[row].data=j;
q[row].lie=x*9+y+1;
}
}
else
{
int a=s[i]-'0';
//x行y列填了一个数字
head.link(++row,x*9+(y+1));
//x行填了一个数字j
head.link(row,x*9+a+81);
//y列填了一个数字j
head.link(row,y*9+a+162);
//g宫填了一个数字j
int xx=x/3*3,yy=y/3*3;
int g=xx/3*3+yy/3+1;
head.link(row,(g-1)*9+a+243);
q[row].pos=i;
q[row].data=a;
q[row].lie=x*9+y+1;
}
}
//cout<<head.S[1]<<endl;
bool ok=head.dance(0);
//cout<<ok<<endl;
//cout<<head.ansd<<endl;
for(int i=0; i<head.ansd; i++)
{
if(s[q[head.ans[i]].pos]=='.')s[q[head.ans[i]].pos]='0'+q[head.ans[i]].data;
}
//cout<<endl;
printf("%s\n",s);
}
return 0;
}
///扩展:
/*
舞蹈连的步骤:
1、从矩阵中选择一行
2、根据定义,标示矩阵中其他行的元素
3、删除相关行和列的元素,得到新矩阵
4、如果新矩阵是空矩阵,并且之前的一行都是1,那么求解结束,跳转到6;新矩阵不是空矩阵,
继续求解,跳转到1;新矩阵是空矩阵,之前的一行中有0,跳转到5
5、说明之前的选择有误,回溯到之前的一个矩阵,跳转到1;如果没有矩阵可以回溯,说明该问题无解,跳转到7
6、求解结束,把结果输出
7、求解结束,输出无解消息
*/