有趣的题目:简单深搜之数独与靶型数独--二进制状压加速与dfs数独剪枝

                                               Sudoku

Time Limit: 2000MS Memory Limit: 65536K

题目链接http://poj.org/problem?id=2676

Description

Sudoku is a very simple task. A square table with 9 rows and 9 columns is divided to 9 smaller squares 3x3 as shown on the Figure. In some of the cells are written decimal digits from 1 to 9. The other cells are empty. The goal is to fill the empty cells with decimal digits from 1 to 9, one digit per cell, in such way that in each row, in each column and in each marked 3x3 subsquare, all the digits from 1 to 9 to appear. Write a program to solve a given Sudoku-task. 

Input

The input data will start with the number of the test cases. For each test case, 9 lines follow, corresponding to the rows of the table. On each line a string of exactly 9 decimal digits is given, corresponding to the cells in this line. If a cell is empty it is represented by 0.

Output

For each test case your program should print the solution in the same format as the input data. The empty cells have to be filled according to the rules. If solutions is not unique, then the program may print any one of them.

Sample Input

1
103000509
002109400
000704000
300502006
060000050
700803004
000401000
009205800
804000107

Sample Output

143628579
572139468
986754231
391542786
468917352
725863914
237481695
619275843
854396127

这题就是个最基本的数独,没有任何要求,只要求一个解就好了,其中0为应该填充的数字,这题博主是刚开始学深搜的时候写的吧。。。挺基础的,我们只需要维护行,列,块的出现的数字就行了,接下来就是正常的深搜了,一个格子一个格子慢慢搜过去。

行和列比较容易判断,块的话我们直接写个函数就好了:

int block(int a,int b) {
	if (a<=3) {
		if (b<=3) return 1;
		else if (b<=6) return 2;
		else return 3;
	} else if (a<=6) {
		if (b<=3) return 4;
		else if (b<=6) return 5;
		else return 6;
	} else {
		if (b<=3) return 7;
		else if (b<=6) return 8;
		else return 9;
	}
}

其中a,b就是x,y坐标

接下来就是简单粗暴的搜索了,从1,1搜到10,1,这个时候,行坐标已经超出了,我们就已经得到一个完整的数独了,记录一下然后全部退出就好了,当然,对于只要一个解的,我们要打个标记,让他找到ans之后不要继续回溯搜索了,不然有点浪费时间,可能会导致TLE。这一题比较水,不用怎么优化都能过,所以并不是我们要讲的重点,所以就贴一下当年有点难看的代码QAQ:

#include <cstdio>
#include <cstring>
int row[10][10],c[10][10],s[10][10],phot[10][10],mark;
int block(int a,int b);
void dfs(int r,int cl);
int main() {
	int n;
	scanf ("%d",&n);
	while (n) {
		n--;
		mark=0;
		memset(row,0,sizeof(row));
		memset(c,0,sizeof(c));
		memset(s,0,sizeof(s));
		memset(phot,0,sizeof(phot));
		for (int i=1; i<=9; i++)
			for (int j=1; j<=9; j++) {
				int ch;
				ch=getchar();
				while (ch<'0' || ch>'9') ch=getchar();
				phot[i][j]=ch-'0';
				if (phot[i][j]) {
					row[i][phot[i][j]]=1;
					c[j][phot[i][j]]=1;
					s[block(i,j)][phot[i][j]]=1;
				}
			}
		dfs(1,1);
		for (int i=1; i<=9; i++) {
			for (int j=1; j<=9; j++)
				printf ("%d",phot[i][j]);
			printf ("\n");
		}
	}
	return 0;
}
int block(int a,int b) {
	if (a<=3) {
		if (b<=3) return 1;
		else if (b<=6) return 2;
		else return 3;
	} else if (a<=6) {
		if (b<=3) return 4;
		else if (b<=6) return 5;
		else return 6;
	} else {
		if (b<=3) return 7;
		else if (b<=6) return 8;
		else return 9;
	}
}
void dfs(int r,int cl) {
	if (cl>9) cl=1,r++;
	if (r>9) {
		mark=1;
		return;
	}
	if (mark) return;
	while (phot[r][cl]) {
		cl++;
		if (cl>9) dfs(r+1,1);
	}
	for (int j=1; j<=9; j++) {
		if (mark) return;
		else if (!phot[r][cl] && !row[r][j] && !c[cl][j] && !s[block(r,cl)][j]) {
			phot[r][cl]=j;
			row[r][j]=1;
			c[cl][j]=1;
			s[block(r,cl)][j]=1;
			dfs(r,cl+1);
			if (mark) return;
			row[r][j]=0;
			c[cl][j]=0;
			s[block(r,cl)][j]=0;
			phot[r][cl]=0;
		} 
	}
}

接下来就是我们的有点难度的靶型数独了:

P1074 靶形数独

题目链接https://www.luogu.org/problem/P1074

时间限制1.00s

内存限制125.00MB

题目描述

小城和小华都是热爱数学的好学生,最近,他们不约而同地迷上了数独游戏,好胜的他们想用数独来一比高低。但普通的数独对他们来说都过于简单了,于是他们向 Z 博士请教,Z 博士拿出了他最近发明的“靶形数独”,作为这两个孩子比试的题目。

靶形数独的方格同普通数独一样,在 9 格宽×9 格高的大九宫格中有9 个 3 格宽×3 格高的小九宫格(用粗黑色线隔开的)。在这个大九宫格中,有一些数字是已知的,根据这些数字,利用逻辑推理,在其他的空格上填入 1 到 9的数字。每个数字在每个小九宫格内不能重复出现,每个数字在每行、每列也不能重复出现。但靶形数独有一点和普通数独不同,即每一个方格都有一个分值,而且如同一个靶子一样,离中心越近则分值越高。(如图)

上图具体的分值分布是:最里面一格(黄色区域)为 10 分,黄色区域外面的一圈(红色区域)每个格子为9分,再外面一圈(蓝色区域)每个格子为8 分,蓝色区域外面一圈(棕色区域)每个格子为7分,最外面一圈(白色区域)每个格子为6分,如上图所示。比赛的要求是:每个人必须完成一个给定的数独(每个给定数独可能有不同的填法),而且要争取更高的总分数。而这个总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和

总分数即每个方格上的分值和完成这个数独时填在相应格上的数字的乘积的总和。如图,在以下的这个已经填完数字的靶形数独游戏中,总分数为 2829。游戏规定,将以总分数的高低决出胜负。

由于求胜心切,小城找到了善于编程的你,让你帮他求出,对于给定的靶形数独,能够得到的最高分数。

输入格式

一共 9 行。每行9个整数(每个数都在 0−9 的范围内),表示一个尚未填满的数独方格,未填的空格用“0”表示。每两个数字之间用一个空格隔开。

输出格式

输出共 1 行。输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数−1。

输入输出样例

输入 

7 0 0 9 0 0 0 0 1 
1 0 0 0 0 5 9 0 0 
0 0 0 2 0 0 0 8 0 
0 0 5 0 2 0 0 0 3 
0 0 0 0 0 0 6 4 8 
4 1 3 0 0 0 0 0 0 
0 0 7 0 0 2 0 9 0 
2 0 1 0 6 0 8 0 4 
0 8 0 5 0 4 0 1 2

输出 

2829

输入 

0 0 0 7 0 2 4 5 3 
9 0 0 0 0 8 0 0 0 
7 4 0 0 0 5 0 1 0 
1 9 5 0 8 0 0 0 0 
0 7 0 0 0 0 0 2 5 
0 3 0 5 7 9 1 0 8 
0 0 0 6 0 1 0 0 0 
0 6 0 9 0 0 0 0 1 
0 0 0 0 0 0 0 0 6

输出 

2852

。。。。中文题,就不讲题意了,这题如果按照之前的暴力写的话会T到怀疑人生,很显然,它是要找到所有的解然后取最大的分数,那么接下来我们就开始我们的剪枝优化了,由于之前是无脑搜的,那么我们现在可以先找到一个搜索状态最少开始搜索,也就是说我们先判断每个空格的能够填的数字有多少个,然后记录状态最少的那个的坐标,然后对它进行搜索:

int x,y,midrow=100;
for (int i=1; i<=9; i++) {
	for (int j=1; j<=9; j++) {
		if (mp[i][j]) continue;
		int yes=0;
		for (int k=1; k<=9; k++) {
			if (!row[i][k] && !col[j][k] && !cell[block(i,j)][k]) yes++;
		}
		if (yes<midrow) x=i,y=j,midrow=yes;
	}
}
for (int k=1; k<=9; k++) {
	if (!row[x][k] && !col[y][k] && !cell[block(x,y)][k]) {
		row[x][k]=1;col[y][k]=1;cell[block(x,y)][k]=1;				
		mp[x][y]=k;
		dfs(cnt-1,goal+get_goal(x,y)*k);
		row[x][k]=0;col[y][k]=0;cell[block(x,y)][k]=0;		
		mp[x][y]=0;
	}
}

这里我们的dfs不再搜索所有格子,而是对空格数cnt进行搜索,当cnt==0的时候那么就也就说明搜索完毕,可以记录他的分值了。

然后在洛谷跑一下。。。。95分,T了一个点,然后最简单的就是手写个O2优化或者开O2了:

以下是AC代码:

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;

const int inf=1e9+10;

int mp[12][12],row[12][12],col[12][12],cell[12][12];
int ans=-1;

int get_goal(int x,int y);
int block(int x,int y);
void dfs(int cnt,int goal);

int main()
{
    int cnt=0,goal=0;
    for (int i=1; i<=9; i++)
        for (int j=1; j<=9; j++){
            cin>>mp[i][j];
            if (mp[i][j]){
                goal+=mp[i][j]*get_goal(i,j);
                row[i][mp[i][j]]=1;
                col[j][mp[i][j]]=1;
                cell[block(i,j)][mp[i][j]]=1;
            }
            else cnt++;
        }
    dfs(cnt,goal);
    cout<<ans<<endl;
    return 0;
}

int get_goal(int x,int y)
{
     if (x==1 || x==9 || y==1 || y==9) return 6;
    if (x==2 || x==8 || y==2 || y==8) return 7;
    if (x==3 || x==7 || y==3 || y==7) return 8;
    if (x==4 || x==6 || y==4 || y==6) return 9;
    return 10;
}

int block(int x,int y)
{
    if (x<=3 && y<=3) return 1;
    if (x<=3 && y<=6) return 2;
    if (x<=3) return 3;
    if (x<=6 && y<=3) return 4;
    if (x<=6 && y<=6) return 5;
    if (x<=6) return 6;
    if (y<=3) return 7;
    if (y<=6) return 8;
    return 9;
}

void dfs(int cnt,int goal)
{
    if (cnt==0) {
        ans=max(goal,ans);
        return;
    }
    int x,y,midrow=100;
    for (int i=1; i<=9; i++){
        for (int j=1; j<=9; j++){
            if (mp[i][j]) continue;
            int yes=0;
            for (int k=1; k<=9; k++){
                if (!row[i][k] && !col[j][k] && !cell[block(i,j)][k]) yes++;
            }
            if (yes<midrow) x=i,y=j,midrow=yes;
        }
    }
    for (int k=1; k<=9; k++){
        if (!row[x][k] && !col[y][k] && !cell[block(x,y)][k]) {
            row[x][k]=1; col[y][k]=1; cell[block(x,y)][k]=1;
            mp[x][y]=k;
            dfs(cnt-1,goal+get_goal(x,y)*k);
            row[x][k]=0; col[y][k]=0; cell[block(x,y)][k]=0;
            mp[x][y]=0;
        }
    }
}

但实际上我们一般是不能这么水过去(滑稽)。。。。

事实上这题需要二进制优化加速,我们将行,列,块的状态都保存为二进制,这个在之前的状压dp里面也讲过了二进制状压,应该不难理解。那么我们的维护数组就变成了row[9]  col[9]   cell[3][3],首先我们先初始化所有需要用到的数组:

int cnt=0,goal=0,x;
for (int i=0; i<9; i++) logg[1<<i]=i;
for (int i=0; i<(1<<9); i++)//枚举所有的填数的状态
	for (int j=i; j; j-=lowbit(j))
		ff[i]++;//对每个状态的有多少个数字进行预处理;
for (int i=0; i<9; i++) {
	row[i]=(1<<9)-1;
	col[i]=(1<<9)-1;
	cell[i/3][i%3]=(1<<9)-1;
}//初始化所有格子都可填,即用111111111填满所有维护的区域

那么对于每个格子所能填的状态为row[x] & col[y] & cell[x/3][y/3]  即每个维护状态的该值都能填。

接下来就是lowbit是使用了,lowbit是将这个数二进制状态下的最后一个1的大小返回,比如对于1100(二进制状态)返回4,那么我们对每个状态枚举的时候就直接扣掉它就是下一个状态了。那么对于dfs里面变化不大,只是做了个二进制的优化而已:

inline void dfs(int cnt,int goal){
    if (cnt==0){
        ans=max(goal,ans);
        return;
    }
    int x,y,ming=20;
    for(int i=0; i<9; i++){
        for (int j=0; j<9; j++){
            if (mp[i][j]) continue;
            int nb=ff[get_sta(i,j)];//所有可填状态的数量
            if (nb<ming) {
                ming=nb;x=i,y=j;
            }
        }
    }
    for (int i=get_sta(x,y); i; i-=lowbit(i)){
        int p=logg[lowbit(i)]+1;
        update(x,y,p);
        dfs(cnt-1,goal+get_goal(x,y)*p);
        update(x,y,-p);
    }
}

。。。。。看起来确实美观(高大上了许多)

以下是无吸氧AC代码:

#include <bits/stdc++.h>
using namespace std;

int row[9],col[9],cell[3][3];
int ans=-1,mp[9][9];
int logg[1<<9],ff[1<<9];

inline int get_goal(int x,int y){
    return min(min(x,y),min(8-x,8-y))+6;
}

inline void update(int x,int y,int val){
    if (val>0) {
        val--;
        row[x]-=1<<val;
        col[y]-=1<<val;
        cell[x/3][y/3]-=1<<val;//将这些格子的这个值抹掉,即不可填
        mp[x][y]=val+1;
    }
    else {
        val=-val;val--;
        row[x]+=1<<val;
        col[y]+=1<<val;
        cell[x/3][y/3]+=1<<val;
        mp[x][y]=0;
    }
}

inline int lowbit(int x){
    return x&-x;
}

inline int get_sta(int x,int y){
    return row[x]&col[y]&cell[x/3][y/3];
}

inline void dfs(int cnt,int goal){
    if (cnt==0){
        ans=max(goal,ans);
        return;
    }
    int x,y,ming=20;
    for(int i=0; i<9; i++){
        for (int j=0; j<9; j++){
            if (mp[i][j]) continue;
            int nb=ff[get_sta(i,j)];//所有可填状态的数量
            if (nb<ming) {
                ming=nb;x=i,y=j;
            }
        }
    }
    for (int i=get_sta(x,y); i; i-=lowbit(i)){
        int p=logg[lowbit(i)]+1;
        update(x,y,p);
        dfs(cnt-1,goal+get_goal(x,y)*p);
        update(x,y,-p);
    }
}

int main()
{
    int cnt=0,goal=0,x;
    for (int i=0; i<9; i++) logg[1<<i]=i;
    for (int i=0; i<(1<<9); i++)//枚举所有的填数的状态
        for (int j=i; j; j-=lowbit(j))
            ff[i]++;//对每个状态的有多少个数字进行预处理;
    for (int i=0; i<9; i++) {
        row[i]=(1<<9)-1;
        col[i]=(1<<9)-1;
        cell[i/3][i%3]=(1<<9)-1;
    }//初始化所有格子都可填,即用111111111填满所有维护的区域
    for (int i=0; i<9; i++)
        for (int j=0; j<9; j++){
            cin>>x;
            if (x) {
                goal+=x*get_goal(i,j);
                update(i,j,x);
            }
            else cnt++;
        }
    dfs(cnt,goal);
    cout<<ans<<endl;
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值