POJ 3074 Sudoku【DancingLinks,数独】


http://poj.org/problem?id=3074
POJ 3074 Sudoku
大意:求解规模为9*9的数独问题
分析:对于每一行,每一列,每一宫,数字1,2。。。9都需出现1次且只能是一次,
可转化为精确覆盖问题,用跳舞链解决
建模型:
行数为9*9*9,数独中,第i行j列放数字k的状态存储在图中第(i*9+j)*9+k行中
列数为9*9+9*9+9*9+9*9,

其中第一个9*9代表第i格是否已填满,
用第二个9*9确保每行的数字唯一且均出现一次
第三个9*9确保每列的数字唯一且出现一次
第四个9*9确保每宫的数字唯一出现1次

#include<stdio.h>
#include<string.h>
const int MAX_COLOUMN = 81+9*9+9*9+9*9+2;//最多出现列数
const int MAX_ROW = 81*9+2;//最多出现的列数

int cnt[MAX_COLOUMN];//cnt[i]统计第i列1的个数
int most,coloumn;
bool ans[MAX_ROW];//ans存放最终选中的行
//跳舞链中的节点
struct Point
{
   int up,down,left,right;//上,下,左,右
   int coloumn;//该点所在的列标
   int row;//行标
}node[MAX_ROW*MAX_COLOUMN+MAX_COLOUMN];

//初始化跳舞链信息为空
void init(int m)
{
 int i;
 for(i=0;i<=m;i++)
 {
  node[i].down=i;
  node[i].up = i;
  node[i].coloumn=i;
  node[i].left=i-1;
  node[i].right=i+1;
  cnt[i]=0;
 }
 node[0].left = m;
 node[m].right = 0;
}

void remove(int c)//删除c列上所有1元素所在的行
{
 node[node[c].right].left=node[c].left;
 node[node[c].left].right=node[c].right;
 int t,tt;
 for(t=node[c].down;t!=c;t=node[t].down)//从上到下从左到右删除该列上的每一非零元素所在行信息
 {
  for(tt = node[t].right;tt!=t;tt=node[tt].right)//删除非零元素所在行
  {
            cnt[node[tt].coloumn]--;
   node[node[tt].down].up = node[tt].up;
   node[node[tt].up].down = node[tt].down;
  }
 }
}

void resume(int c)//还原c列上所有1元素所在的行
{
 int t,tt;
 for(t=node[c].up;t!=c;t=node[t].up)//从下往上从左到右还原该c列中1所在的行信息
 {
  for(tt=node[t].left;tt!=t;tt=node[tt].left)
  {
   cnt[node[tt].coloumn]++;
   node[node[tt].up].down=tt;
   node[node[tt].down].up=tt;
  }
 }

 node[node[c].right].left=c;
 node[node[c].left].right=c;
}

bool dfs(int k)//k为已经选中的行的数目
{
 int i,j;
 if(k>=most)return false;
 if(node[coloumn].right == coloumn)//当前跳舞链已为空
 {
  if(k<most)
   most = k;
  return true;
 }

 int t = coloumn+1;
 int c;
 //选取当前矩阵中1最少的列
 for(i=node[coloumn].right;i!=coloumn;i=node[i].right)
 {
  if(cnt[i]<t)
  {
   c=i;t=cnt[i];
   if(t==1)break;
  }
 }
    
 remove(c);//删除列c中所有1所在的行

 //删除时从左到右从上到下,还原时从下到上,从右到左
 for(i = node[c].down;i!=c;i=node[i].down)
 {
  for(j=node[i].right;j!=i;j=node[j].right)
  {
   remove(node[j].coloumn);
  }
  ans[node[j].row]=true;
  if(dfs(k+1))
  {
   return true;
  }
  ans[node[j].row]=false;
  for(j=node[j].left;j!=i;j=node[j].left)
  {
   resume(node[j].coloumn);
  }

  
 }

 resume(c);
 return false;
}
bool graph[MAX_ROW][MAX_COLOUMN];
void addrow(int i,int j,int k)
{
    int curr = (i*9+j)*9+k;
 graph[curr][(i*9+j)]=true;
 graph[curr][81+i*9+k]=true;
 graph[curr][81+81+j*9+k]=true;
    int tr = i/3;
 int tc = j/3;
 graph[curr][81+81+81+(tr*3+tc)*9+k]=true;
}

char str[MAX_ROW];
int main()
{
 int N,M,i,j,k;
 while(scanf("%s",str)!=EOF)
 {
  if(strcmp(str,"end")==0)break;
  N=81*9;
  M = 9*9+9*9+9*9+9*9;
  coloumn = M;
  int cur=coloumn+1;//当前节点编号
  init(coloumn);
  memset(graph,0,sizeof(graph));
  for(i=0;i<9;i++)
   for(j=0;j<9;j++)
   {
    if(str[i*9+j]=='.')
    {
     for(k=0;k<9;k++)//遍历每一种颜色
     {
       addrow(i,j,k);
     }
     continue;
    }
    k = str[i*9+j]-'1';
       addrow(i,j,k);
   }
  for(i=0;i<N;i++)
  {
   int start = cur;//记录第i列的开始点编号
   int pre = cur;//记录该列中当前1的左边第一个1编号
   for(j=0;j<M;j++)
   {
   // scanf("%d",&n);
    if(graph[i][j])//跳舞链中仅插入非0元素
    {
     int pos = j;
     node[cur].up = node[pos].up;
     node[node[pos].up].down = cur;
        node[cur].down = pos;
     node[pos].up = cur;
     cnt[pos]++;//该列1的个数+1
     node[cur].coloumn = pos;
     node[cur].left = pre;
     node[pre].right = cur;
     node[cur].right = start;
                    node[start].left=cur;
     node[cur].row = i;
     pre=cur++;
    }
   }
  }

 
  most = N+1;//记录最少需要选中的行数
  memset(ans,false,sizeof(ans));
  dfs(0);
   // printf("Yes, I found it\n");
  for(i=0;i<81;i++)
   for(j=0;j<9;j++)
    if(ans[i*9+j])
    {
     printf("%d",j+1);
     break;
    }

    printf("\n");
 
 }
 return 0;
}

转载于:https://www.cnblogs.com/AndreMouche/archive/2011/02/24/1964136.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值