【dfs预处理+DLX】hdu 4069 Squiggly Sudoku

Source :hdu 4069 Squiggly Sudoku

http://acm.hdu.edu.cn/showproblem.php?pid=4069


Problem Description
Today we play a squiggly sudoku, The objective is to fill a 9*9 grid with digits so that each column, each row, and each of the nine Connecting-sub-grids that compose the grid contains all of the digits from 1 to 9.
Left figure is the puzzle and right figure is one solution.
Now, give you the information of the puzzle, please tell me is there no solution or multiple solution or one solution.
这里写图片描述
Input
The first line is a number T(1<=T<=2500), represents the number of case. The next T blocks follow each indicates a case.
Each case contains nine lines, Each line contains nine integers.
Each module number tells the information of the gird and is the sum of up to five integers:
0~9: ‘0’ means this gird is empty, ‘1’ - ‘9’ means the gird is already filled in.
16: wall to the up
32: wall to the right
64: wall to the down
128: wall to the left
I promise there must be nine Connecting-sub-grids, and each contains nine girds.

Output
For each case, if there are Multiple Solutions or no solution just output “Multiple Solutions” or “No solution”. Else output the exclusive solution.(as shown in the sample output)

题意

9X9的数独游戏,满足每行、每列、每宫填满1-9,注意“宫”的形状变化了!

示例

Sample Input
3
144 18 112 208 80 25 54 144 48
135 38 147 80 121 128 97 130 32
137 32 160 144 114 167 208 0 32
192 100 160 160 208 96 183 192 101
209 80 39 192 86 48 136 80 114
152 48 226 144 112 160 160 149 48
128 0 112 166 215 96 160 128 41
128 39 153 32 209 80 101 136 35
192 96 200 67 80 112 208 68 96

144 48 144 81 81 16 53 144 48
128 96 224 144 48 128 103 128 38
163 208 80 0 37 224 209 0 32
135 48 176 192 64 112 176 192 104
192 101 128 89 80 82 32 150 48
149 48 224 208 16 48 224 192 33
128 0 114 176 135 0 80 112 169
137 32 148 32 192 96 176 144 32
192 96 193 64 80 80 96 192 96

144 88 48 217 16 16 80 112 176
224 176 129 48 128 40 208 16 37
145 32 128 96 196 96 176 136 32
192 32 227 176 144 80 96 192 32
176 192 80 98 160 145 80 48 224
128 48 144 80 96 224 183 128 48
128 36 224 144 51 144 32 128 105
131 64 112 136 32 192 36 224 176
224 208 80 64 64 116 192 83 96

Sample Output

Case 1:
521439678
763895124
984527361
346182795
157964832
812743956
235678419
479216583
698351247
Case 2:
No solution
Case 3:
Multiple Solutions


思路

dfs预处理给所有的小格确定所属的方阵编号,然后DLX精确覆盖即可,这题收获很大,有以下三点:

1.dlx转换为精确覆盖问题时,列任务有4个,分别构成N×N×4列,其中sub(a,b)表示第a个子方阵要有字母b,普通的数独游戏根据元素(r,c)在第r行第c列就可以确定属于哪个a方阵,这道题特殊就特殊在方阵形状变了,你需要重新给(r,c)所属方阵编号了,怎么做?dfs所有点,利用位运算判断是否越界,预处理得到所有方格(i,j)所属方阵编号block[ i ][ j ];再构造724×324矩阵时把sub(a,b)替换为block[ r ][ c ]就ok了!

2.难点!做着做着你会发现,解的个数与最终的解不可兼得,因为回溯!所以中间的答案没有得到保留。怎么破?!当解的个数为1时,把答案ans存起来,最终要是解的个数就是1,直接输出刚才保存的结果,否则就是多解,我们也不用把所有的结果都存起来了。

3.成也剪枝!结果没问题,但最终还是TLEQAQ,什么情况?处理解的个数,当为2时就return好了,不用继续dlx了,做题和解决问题还是有点区别啊Orz..

补充:如果遇到要求出所有的解,每到递归基就打印一次,没必要存储所有结果了!模板dlx输出的是第一组可行解,至于有没有其他的不知道,把dfs里面的return去掉就可以了。

这里写图片描述


参考代码

/*====================================*\
|*     舞蹈链    DLX                  *|
\*====================================*/
/*Author:Hacker_vision*/
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<vector>
#include<map>
#include<climits>
#define clr(k,v) memset(k,v,sizeof(k))
#define ll long long
#define eps 1e-8
using namespace  std;

const int SLOT = 0;
const int ROW = 1;
const int COL = 2;
const int SUB = 3;
const int N = 9;
const int M = 3;
const int maxr = N*N*N + 10;//行数
const int maxn = N*N*4 + 10;//列数
const int maxnode = maxr*maxn + 10;//总节点数
int tot,g[12][12];
int top,temp[maxr];

struct DLX{
  int n,sz;//列数,节点总数
  int S[maxn];//各列节点数
  int row[maxnode],col[maxnode];//各节点行列编号
  int L[maxnode],R[maxnode],U[maxnode],D[maxnode];//十字链表,用数组写的
  int ansd,ans[maxr];//解
  void init(int n){ //n是列数
   this->n = n;
   //虚拟节点
   for(int i = 0; i <= n; ++ i){
     U[i] = i;D[i] = i;L[i] = i - 1;R[i] = i + 1;
   }
   R[n] = 0;L[0] = n;
   sz = n + 1;
   memset(S,0,sizeof(S));
  }
  void addRow(int r,vector<int>columns){
   int first= sz;
   for(int i = 0; i < columns.size(); ++ i){
    int c = columns[i];
    L[sz] = sz - 1;R[sz] = sz + 1;D[sz] = c;U[sz] = U[c];
    D[U[c]] = sz;U[c] = sz;
    row[sz] = r;col[sz] = c;
    S[c]++;sz++;
   }
   R[sz - 1] = first;L[first] = sz - 1;
  }
  //顺着链表A,遍历除s外的其他元素
 // #define FOR(i,A,s) for(int i = A[s]; i != s;i = A[i])
  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[col[j]];
      }
  }
  void restore(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(int d){
    if(R[0] == 0){
        ansd = d;//选了ansd行
        tot++;
        if(tot == 1){
            top = ansd;//ansd是局部变量,拿出来
            for(int i = 0; i < ansd; ++ i) temp[i] = ans[i];
        }
        return true;
    }
    //找S的最小列
    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]){
        ans[d] = row[i];
        for(int j = R[i]; j != i; j = R[j]) remove(col[j]);
        dfs(d+1);
        if(tot > 1) return true;
        for(int j = L[i]; j != i; j = L[j]) restore(col[j]);
    }
    restore(c);
    return false;
  }
  bool solve(vector<int>&v){//solve函数这里就没用了,因为回溯!
    v.clear();
    if(!dfs(0)) return false;
    for(int i = 0; i < ansd; ++ i) v.push_back(ans[i]);
    return true;
  }
};

//行、列的统一编解码函数。从1开始编号
int encode(int a,int b,int c){
  return a*N*N + b*N + c + 1;
}

void decode(int code,int& a,int& b,int& c){
  code--;
  c = code%N; code/=N;
  b = code%N; code/=N;
  a = code;
}
//block[][]也可以当做vis[][]用,比较巧妙
int block[12][12],dir[4][2]={-1,0,0,1,1,0,0,-1};//四个方向,16,32,64,128决定的方向
void dfs(int x,int y){//函数重载,哈哈!
  block[x][y] = tot;
  for(int i = 0; i < 4; ++ i){
    int xx = x + dir[i][0];
    int yy = y + dir[i][1];
    if(xx>=0&&yy>=0&&xx<N&&yy<N&&block[xx][yy]==-1&&(g[x][y]&(1<<(i+4)))==0){
       dfs(xx,yy);                                  //g[x][y]&(1<<(1+4)) == 0,说明g[x][y]没有32,可以往右走,dir方向很重要
    }
  }
}

DLX dlx;

int main(){
  #ifndef ONLINE_JUDGE
  freopen("input.txt","r",stdin);
  #endif // ONLINE_JUDGE
  int T;cin>>T;int cnt=1;
  while(T--){
    for(int i = 0; i < N; ++ i)
        for(int j = 0;j < N; ++ j)
        scanf("%d",&g[i][j]);
    //预处理,得到block[r][c],确定(r,c)所在方阵编号0-8
    clr(block,-1);
    tot = 0;//初始化方阵编号
    for(int i = 0; i < N; ++ i)//遍历所有点确定所有点的编号,最好情况下有效循环次数9次
     for(int j = 0; j < N; ++ j)
     if(block[i][j] == -1){
      dfs(i,j);
      tot++;
     }
    //跑DLX即可,注意列任务的第4个传参的含义
    dlx.init(N*N*4);//列数
    for(int r = 0; r < N; ++ r)
      for(int c = 0; c < N; ++ c)
        for(int v = 0; v < N; ++ v){
          int k = g[r][c]&15;//取低四位!!!!!
          if(k == 0||k == 1 + v){
          vector<int>columns;
          columns.push_back(encode(SLOT,r,c));//列数分四种情况,r行c列要有数字
          columns.push_back(encode(ROW,r,v));//r行要有数字v
          columns.push_back(encode(COL,c,v));//c列要有数字v
          columns.push_back(encode(SUB,block[r][c],v));//第x个方阵要有数字v,之前根据(r,c)大小直接确定第几个方阵,现在需要预处理一下,block[r][c]返回(r,c)所属方阵编号
          dlx.addRow(encode(r,c,v),columns);//上一步传参,方阵编号(0~8)
          }
        }
   tot = 0;
   dlx.dfs(0);
   //控制输出
   printf("Case %d:\n",cnt++);
  // printf("解得个数tot = %d\n",tot);
   if(!tot) puts("No solution");
   else if(tot > 1) puts("Multiple Solutions");
   else{
    for(int i = 0; i < top; ++ i){
        int r,c,v;
        decode(temp[i],r,c,v);
        g[r][c] = 1 + v;
    }
    for(int i = 0; i < N; ++ i){
        for(int j = 0; j < N; ++ j)
            printf("%d",g[i][j]);
        puts("");
    }
   }
  }
  return 0;
}
  • 加粗 Ctrl + B
  • 斜体 Ctrl + I
  • 引用 Ctrl + Q
  • 插入链接 Ctrl + L
  • 插入代码 Ctrl + K
  • 插入图片 Ctrl + G
  • 提升标题 Ctrl + H
  • 有序列表 Ctrl + O
  • 无序列表 Ctrl + U
  • 横线 Ctrl + R
  • 撤销 Ctrl + Z
  • 重做 Ctrl + Y
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值