数独问题ZOJ 3122:Sudoku

题意:数独,每行,每列,每宫内均要出现且只出现一次‘A’-‘P'这16个字母

输出满足条件的一种情况,否则输出-1

前置知识:DLX:Dancing Links(使用前提:稀疏矩阵)

题型1:精确覆盖问题:n行m列至少选多少行使得每列中恰好有一个1

题型2:重复覆盖问题:n行m列至少选多少行使得每列中至少有一个1

Luogu P4929 【模板】舞蹈链(DLX)

B31【模板】舞蹈链(DLX)精确覆盖问题_哔哩哔哩_bilibili

舞蹈链问题中,如果枚举所有情况是每一行选或者不选(2^n),最后还要检查是否满足精确覆盖(n*m)时间复杂度O(n*m*2^n),所以引入了舞蹈链算法,可以在较低的时间复杂度内解决稀疏矩阵的精确覆盖问题

很多关于精确覆盖的问题都可以转化为DLX,比如:

Treasure Map  ZOJ - 3209 

题目给定初始矩形(1<=n,m<=30),输入几个小矩形,问如何使用最少的小矩形来完全表示初始矩形,要求不能重复覆盖,这一类题目就可以转化为DLX问题,把所有的小矩形展开变成01序列,每一列01代表矩形的位置,每一行代表每一个小矩形。

回到这道题目可以发现,每个点如果未知的话有十六种情况,如果已知的话就是一种情况,尝试转化成01矩阵,用列代表依赖,行代表每一个选择

首先找到每一列

可以发现:

每一个点有四个依赖:①当前坐标②某一行的A~P③每一列的A~P④每一宫的A~P,

①:当前坐标(i,j)填入一个字母对应256个位置-->256列

②:1~16行每一个位置可以填A~P---------------->256列

③:1~16列每一个位置可以填A~P---------------->256列

④:1~16宫每一个宫可以填A~P------------------->256列

然后一共256*4=1024列,恰好填满每一列才能表示正确的答案

(PS:形象一点说,如果此时1024列都填满了代表什么:①每个位置填满②16行每一行都填满了A~P③16列每一列都填满了A~P④16宫每一宫都填满了A~P)

接下来是每一行:

假设(0,0)这个点未填数字,那么要枚举A~P的16中情况,假设填了'C',代表①②③④对应的位置都要有一个1

①:当前坐标(0,0)-->第1列填1(代表1行1列有数字)

②:在第1行填C----->第256+3列填1(256代表偏移量因为前面①占了256个位置,3代表第1行第3个字母)

③:在第1列填C----->第256*2+3列填1(3代表第1列第3个字母)

④:在第1宫填C----->第256*3+3列填1(3代表第1宫第3个字母)

假设(0,0)这个点填了字母,那么要把这个点的的状态记录下来,记录方法同上,并且不用担心这个点到时候跑DLX的时候不被选到,因为要精确覆盖每一个点的话,这个点一定要选,因为有①条件,代表(0,0)这个点至少要选一个状态,而已有字母的情况下只有一个状态,那么肯定要选这个状态

知道了思路之后,还有一个难点就是如何把每个坐标的每一个情况转化到列上,以及已知所有行的选择情况之后如何把每一行的状态又转化回A~P?

一:把情况转化为列号:

首先遍历每个点(i,j),把A~P由1~16编号(假设为k),遍历每个编号

那么这个点对应的行号就是i*16*16+j*16+k

①情况的列号:i*16+j+1

②情况的列号256+i*16+k

③情况的列号256*2+j*16+k

④情况的列号256*3+(i/4*4+j/4)*16+k;

二:把所有选择的行转化为坐标和答案(A~P):

假设此时选择的行号为x

横坐标(x-1)/16/16

纵坐标(x-1)/16%16

当前位置的答案(x-1)%16+1;

于是本题得解:

代码实现:

#include <map>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define pp pop_back()
#define int long long
#define laile cout<<"laile"<<endl
#define lowbit(x) ((x)&(-x))
#define double long double
#define sf(x) scanf("%lld",&x)
#define sff(x,y) scanf("%lld %lld",&x,&y)
#define _for(i,n) for(int i=0;i<(n);++i)
#define _rep(i,a,b) for(int i=(a);i<=(b);++i)
#define all(x) (x).begin(), (x).end()
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
typedef unsigned long long ULL;
typedef pair<int,int>PII;
const int N=5e6,INF=4e18;
int n,m;
int r[N],l[N],d[N],u[N];
int row[N],col[N],idx;
int h[N];//每行头节点
int s[N];//列元素个数
int res[N];
char ans[20][20];
string str[20];
void init()
{
	for(int i=0;i<=m;i++)
	{
		u[i]=i,d[i]=i;
		l[i]=i-1,r[i]=i+1;
	}
	l[0]=m,r[m]=0;
	idx=m;
	return;
}
void remove(int y)
{
	r[l[y]]=r[y],l[r[y]]=l[y];
	for(int i=d[y];i!=y;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]]--;
	return ;
}
void remake(int y)
{
	r[l[y]]=y,l[r[y]]=y;
	for(int i=u[y];i!=y;i=u[i])
		for(int j=r[i];j!=i;j=r[j])
			u[d[j]]=j,d[u[j]]=j,s[col[j]]++;
	return ;
}
bool space;
bool dance(int dep)
{
	if(!r[0])
	{
		_for(i,dep)
		{
			//	int now=i*16*16+j*16+k;(i:[0,15],j:[0,15],k:[1,16])
			int x=(res[i]-1)/16/16;//PS:不整除的话不能直接把除号分给被除数每个元素
			int y=(res[i]-1)/16%16;
			int k=(res[i]-1)%16+1;
			ans[x][y]=k;
		}
		_rep(i,0,15)
		{
			_rep(j,0,15)
			{
				cout<<(char)(ans[i][j]+'A'-1);
			}
			cout<<endl;
		}
		
		return true;
	}
	int y=r[0];
	for(int i=r[0];i;i=r[i])
		if(s[i]<s[y])y=i;
	remove(y);
	for(int i=d[y];i!=y;i=d[i])
	{
		res[dep]=row[i];
		for(int j=r[i];j!=i;j=r[j])remove(col[j]);
		if(dance(dep+1))return true;
		for(int j=l[i];j!=i;j=l[j])remake(col[j]);
	}
	remake(y);
	return false;
}
void add(int x,int y)
{
	row[++idx]=x,col[idx]=y;
	s[y]++;	
	u[idx]=u[y];
	d[u[y]]=idx;
	d[idx]=y;
	u[y]=idx;
	if(!h[x])h[x]=r[idx]=l[idx]=idx;
	else
	{
		l[idx]=l[h[x]];
		r[l[h[x]]]=idx;
		r[idx]=h[x];
		l[h[x]]=idx;
	}
}
void solve()
{
	
	while(cin>>str[0])
	{
		if(space)cout<<endl;
		space=true;
		n=4096,m=1024;
		for(int i=0;i<=n;i++)h[i]=0;
		for(int j=0;j<=m;j++)s[j]=0;
		idx=0;
		init();
		_for(i,16)
		{
			if(i)cin>>str[i];
			_for(j,16)
			{
				char c;
				int a;
				c=str[i][j];
				if(c=='-')a=0;
				else a=c-'A'+1;
				_rep(k,1,16)
				{
					if(!a||a==k)
					{
						int x=i*16*16+j*16+k;
						add(x,i*16+j+1);
						add(x,256+i*16+k);
						add(x,256*2+j*16+k);
						add(x,256*3+(i/4*4+j/4)*16+k);
					}
				}
			}
		}
		dance(0);
	}
	return;
}
signed main()
{
	int T=1;
//	cin>>T;
	while(T--)
		solve();
	return 0;
}

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值