20210914下午

20210914下午

搜索?狗都不用。
请添加图片描述

T1T2T3T4T5
预测100100010060
一测10010001000

T1:
简单搜索。。。按顺序搜就行
T2:
提示把剪枝都讲了,实在是太良心了。
T3:
不是,凭什么 n = 8000 n=8000 n=8000能搜出来啊。。。我本来想写 10 10 10 h a s h hash hash暴力了。。。
请添加图片描述
爆搜能搜出来。。。而且题面是 N o    s o l u t i o n ! No\ \ solution! No  solution!,答案是 N o    S o l u t i o n ! No\ \ Solution! No  Solution!
T4:
经典搜索加模拟,其实蛮简单的,只有消去和坠落两个操作要注意。
一些剪枝:
1.如果当前同一颜色只有 1 1 1块或 2 2 2块就结束游戏,无解。
2.只有右边为空时才往左边移,否则一定往右移更优。
3.如果是相同色块就无需交换。
贴个代码吧。

struct game{ //游戏状态
	int s[10][10]; //块色
	int col[15];  //每种颜色个数,用于剪枝1
	bool cl[10][10];  //清除标记
	void Read()  //读入
	{
		int tmp;
		for(int i=1;i<=5;i++) for(int j=1;scanf("%d",&tmp)&&tmp;j++) s[i][j]=tmp;
		return;
	}
	bool empty() //是否消完
	{
		for(int i=1;i<=5;i++) for(int j=1;j<=7;j++) if(s[i][j]) return false;
		return true;
	}
	bool clear() //消块
	{
		bool res=false;
		for(int i=1;i<=5;i++)
		{
			for(int j=1,k,c;j<=7;j++)
			{
				c=s[i][j];if(!c) continue;
				for(k=i;k<=5&&s[k+1][j]==c;k++); //向右拓展
				if(k-i+1>=3) {res=true;for(int l=i;l<=k;l++) cl[l][j]=true;}
				for(k=j;k<=7&&s[i][k+1]==c;k++); //向上拓展
				if(k-j+1>=3) {res=true;for(int l=j;l<=k;l++) cl[i][l]=true;}
			}
		}
		for(int i=1;i<=5;i++) for(int j=1;j<=7;j++) if(cl[i][j]) s[i][j]=0,cl[i][j]=false; //消去
		return res;
	}
	void fall()  //坠落
	{
		for(int j=2;j<=7;j++) for(int i=1;i<=5;i++) for(int k=j;k>1&&s[i][k]&&!s[i][k-1];k--) swap(s[i][k],s[i][k-1]);  //下面为空就一直掉
		return;
	}
	bool over() //剪枝1
	{
		memset(col,0,sizeof(col));
		for(int i=1;i<=5;i++) for(int j=1;j<=7;j++) col[s[i][j]]++;
		for(int i=1;i<=10;i++) if(col[i]>=1&&col[i]<=2) return true;
		return false;
	}
}st;


void Dfs(int x,game now) //搜索
{
	if(flag) return;
	now.fall(); //可能上次移向空处 
	while(now.clear()) now.fall(); //能消就一直消
	if(now.over()) return; 
	if(x==n+1)
	{
		if(now.empty()) print(),flag=true;
		return;
	}
	game nxt;nxt=now;
	for(int i=1;i<=5;i++)
	{
		for(int j=1;j<=7;j++)
		{
			if(!now.s[i][j]) continue;
			if(in(i+1,j)&&now.s[i+1][j]!=now.s[i][j]) //剪枝3
			{
				swap(nxt.s[i+1][j],nxt.s[i][j]);
				ans[x].x=i;ans[x].y=j;ans[x].f=1;
				Dfs(x+1,nxt);
				if(flag) return;
				swap(nxt.s[i+1][j],nxt.s[i][j]);
			}
			if(!now.s[i-1][j]) //剪枝2
			{
				if(!in(i-1,j)||now.s[i-1][j]==now.s[i][j]) continue; //剪枝3
				swap(nxt.s[i-1][j],nxt.s[i][j]);
				ans[x].x=i;ans[x].y=j;ans[x].f=-1;
				Dfs(x+1,nxt);
				if(flag) return;
				swap(nxt.s[i-1][j],nxt.s[i][j]);
			}
		}
	}
	return;
}

思路很清晰的模拟。
T5:
《关于我用搜索怎么剪都剪不过就去学 D a n c i n g    L i n k s    X Dancing\ \ Links\ \ X Dancing  Links  X然后 40 m s 40ms 40msA了数独这件事》
请添加图片描述
搜索我就不多说了,就算我加了按最小选择排序也过不了,可能是我的问题。
下面我来隆重介绍 D a n c i n g    L i n k s    X Dancing\ \ Links\ \ X Dancing  Links  X算法。
D L X DLX DLX是由 X X X算法通过十字链表优化而来,用于解决精准覆盖问题。
什么是精准覆盖问题?
可以理解为给出 n n n个集合和一个全集,要求选出一些集合使他们不重复地构成全集,也就是 S 1 ∪ S 2 ∪ S 3 ⋯ = S S_1\cup S_2 \cup S_3 \dots =S S1S2S3=S S 1 ∩ S 2 ∩ S 3 ⋯ = ∅ S_1 \cap S_2 \cap S_3 \dots=\varnothing S1S2S3=
举个例子,现在有集合
S = { 114 , 233 , 514 , 7 , 1926 , 817 , 666 } S=\{114,233,514,7,1926,817,666\} S={114,233,514,7,1926,817,666}
S 1 = { 114 , 514 , 7 } S_1=\{114,514,7\} S1={114,514,7}
S 2 = { 114 , 1926 , 817 , 666 } S_2=\{114,1926,817,666\} S2={114,1926,817,666}
S 3 = { 514 , 817 , 666 } S_3=\{514,817,666\} S3={514,817,666}
S 4 = { 114 } S_4=\{114\} S4={114}
S 5 = { 114 , 233 , 7 , 1926 } S_5=\{114,233,7,1926\} S5={114,233,7,1926}
S 6 = { 514 , 7 , 1926 , 66 } S_6=\{514,7,1926,66\} S6={514,7,1926,66}
S 7 = { 233 , 514 , 7 } S_7=\{233,514,7\} S7={233,514,7}
S 8 = { 233 , 817 } S_8=\{233,817\} S8={233,817}
可以一眼看出这个问题的精准覆盖为 S 2 S_2 S2 S 7 S_7 S7 S 3 S_3 S3 S 5 S_5 S5 S 4 S_4 S4 S 6 S_6 S6 S 8 S_8 S8
那么 X X X算法是如何解决这个问题的呢?
首先,我们把这些关系转化为矩阵
S 114 233 514 7 1926 817 666 S 1 1 0 1 1 0 0 0 S 2 1 0 0 0 1 1 1 S 3 0 0 1 0 0 1 1 S 4 1 0 0 0 0 0 0 S 5 1 1 0 1 1 0 0 S 6 0 0 1 1 1 0 1 S 7 0 1 1 1 0 0 0 S 8 0 1 0 0 0 1 0 \begin{matrix} S&114&233&514&7&1926&817&666 \\ S_1&1&0&1&1&0&0&0 \\ S_2&1&0&0&0&1&1&1 \\ S_3&0&0&1&0&0&1&1 \\ S_4&1&0&0&0&0&0&0 \\ S_5&1&1&0&1&1&0&0 \\ S_6&0&0&1&1&1&0&1 \\ S_7&0&1&1&1&0&0&0 \\ S_8&0&1&0&0&0&1&0 \end{matrix} SS1S2S3S4S5S6S7S81141101100023300001011514101001107100011101926010011008170110000166601100100
然后,我们先假设选择了 S 1 S_1 S1,显然我们就不能选那些在 S 1 S_1 S1上有数的集合,那么我们就去删除这些行和列
请添加图片描述

得到
S 233 1926 817 66 S 8 1 0 1 0 \begin{matrix} S&233&1926&817&66 \\ S_8 &1&0&1&0 \end{matrix} SS82331192608171660
接着我们选择 S 8 S_8 S8并进行同样的操作。
S 1926 66 \begin{matrix} S&1926&66 \end{matrix} S192666
发现集合被删完了, S S S却还有剩,说明选 S 1 S_1 S1无法得到答案,于是回溯恢复被删除的行和列并不再考虑 S 1 S_1 S1
之后选择 S 2 S_2 S2
请添加图片描述

得到
S 233 514 7 S 7 1 1 1 \begin{matrix} S&233&514&7 \\ S_7&1&1&1 \end{matrix} SS72331514171
选择 S 7 S_7 S7 S S S被删完,说明找到一组答案,若不是多组答案可以直接退出程序了。
若要找多组答案,则继续回溯,选择 S 3 S_3 S3
请添加图片描述

得到
S 114 233 7 1926 S 4 1 0 0 0 S 5 1 1 1 1 \begin{matrix} S&114&233&7&1926 \\ S_4&1&0&0&0 \\ S_5&1&1&1&1 \end{matrix} SS4S51141123301701192601
若选择 S 4 S_4 S4,那么有
S 233 7 1926 \begin{matrix} S&233&7&1926 \end{matrix} S23371926
无解,回溯选择 S 5 S_5 S5,发现消完,找到另一组解。
回溯选择 S 4 S_4 S4
请添加图片描述
得到
S 233 514 7 1926 817 666 S 6 0 1 1 1 0 1 S 7 1 1 1 0 0 0 S 8 1 0 0 0 1 0 \begin{matrix} S&233&514&7&1926&817&666 \\ S_6&0&1&1&1&0&1 \\ S_7&1&1&1&0&0&0 \\ S_8&1&0&0&0&1&0 \end{matrix} SS6S7S823301151411071101926100817001666100
选择 S 6 S_6 S6,得到
S 233 817 S 8 1 1 \begin{matrix} S&233&817 \\ S_8&1&1 \end{matrix} SS823318171
删去后再次得到一组解。
后面就不模拟了(做图做麻了,不过大概意思应该都知道了,这就是 X X X算法的大致流程。
可以发现此算法需要大量的删除行列和恢复的操作,所以如果朴素实现,其指数级复杂度取决于矩阵中 1 1 1的个数,大概只能接受 n , m ≤ 100 n,m\le 100 n,m100的矩阵。
所以我们的十字链表优化就来啦, D L X DLX DLX横空出世。(其实并没有,它也不是啥省选内容。
下面偷一下DLX 详细讲解的图来简单介绍一下十字链表(我真的做不来啊淦。
请添加图片描述
十字链表拥有四个指针左 l l l r r r u u u d d d,以这些指针来存储一个矩阵。
完整版大概是张这样的。
请添加图片描述
当然,除了上面的几个数组,还有 f i r s t i first_i firsti表示每一行的行首,每一列的列首则是由 0 0 0节点连接出的虚拟节点。
另外需要一个 s i z i siz_i sizi记录每列的节点个数。
所以大概需要这几个数组

int n,m,tot,first[N],siz[N]; //tot表示节点编号
int l[N],r[N],u[N],d[N];
int row[N],col[N]; //表示该节点的行列

接下来是几个操作
1.建表
构建一个 R × C R\times C R×C的空矩阵,只需将每一列的虚节点建出来就行。

void build(int R,int C)
{
	n=R;m=C;
	for(int i=0;i<=C;i++) l[i]=i-1,r[i]=i+1,u[i]=d[i]=i;
	l[0]=C;r[C]=0;tot=C; //每一行列都是环状链表,新节点从C+1开始
	memset(first,0,sizeof(first));
	memset(siz,0,sizeof(siz));
	return;
}

另外建议使用以下形式访问环状链表

#define IT(i,A,x) for(int i=A[x];i!=x;i=A[i])

2.插入
( R , C ) (R,C) (R,C)处插入一个点,相当于我们在矩阵内插入一个 1 1 1
定义该节点为 t o t tot tot,在列上,直接进行操作。
t o t tot tot上方改为 C C C t o t tot tot下方改为原来 C C C下方的节点,原来 C C C下方节点的上方改为 t o t tot tot C C C下方节点改为 t o t tot tot

u[tot]=C,d[tot]=d[C],u[d[C]]=tot,d[C]=tot;

对行,进行分类。
若该行还没有 f i r s t first first节点,将 f i r s t first first直接改为 t o t tot tot
若该行有 f i r s t first first节点了,将 t o t tot tot左边改为 f i r s t R first_R firstR,右边改为原来 f i r s t R first_R firstR的右边, f i r s t R first_R firstR右边节点的左边改为 t o t tot tot f i r s t R first_R firstR的右边改为 t o t tot tot

if(!first[R]) first[R]=l[tot]=r[tot]=tot;
else l[tot]=first[R],r[tot]=r[first[R]],l[r[first[R]]]=tot,r[first[R]]=tot;

都是很好理解的链表操作,完整如下

void insert(int R,int C)
{
	row[++tot]=R;col[tot]=C;siz[C]++;
	u[tot]=C,d[tot]=d[C],u[d[C]]=tot,d[C]=tot;
	if(!first[R]) first[R]=l[tot]=r[tot]=tot;
	else l[tot]=first[R],r[tot]=r[first[R]],l[r[first[R]]]=tot,r[first[R]]=tot;
	return;
}

3.移除
对于移除列 C C C的操作,我们需要移除这一列与向下访问到的每一行。
移除这一列很简单,将 C C C左右节点的左右指针修改即可。

l[r[C]]=l[C];r[l[C]]=r[C];

接着向下访问每个节点,再横向访问哪一行,将节点上下节点修改以删去那一行。

IT(i,d,C) IT(j,r,i) u[d[j]]=u[j],d[u[j]]=d[j],siz[col[j]]--;

完成修改操作。

void remove(int C)
{
	l[r[C]]=l[C];r[l[C]]=r[C];
	IT(i,d,C) IT(j,r,i) u[d[j]]=u[j],d[u[j]]=d[j],siz[col[j]]--;
	return;
}

4.恢复
其实就是把移除操作反过来弄一遍。

void recover(int C)
{
	IT(i,d,C) IT(j,r,i) u[d[j]]=d[u[j]]=j,siz[col[j]]++;
	l[r[C]]=r[l[C]]=C;
	return;
}

以上就是我们需要的所有操作,然后与 X X X算法结合就是 D L X DLX DLX算法了。

bool dance(int dep,int *s)
{
	int C=r[0]; //若0无右节点,说明矩阵为空,算法结束
	if(!C) {···;return true;} //处理答案
	IT(i,r,0) if(siz[i]<siz[C]) C=i; //找一个结点最少的列以减少搜索树
	remove(C);
	IT(i,d,C)
	{
		s[dep]=row[i];
		IT(j,r,i) remove(col[j]);
		if(dance(dep+1,s)) return true; //镜像操作,若多组数据就不用return
		IT(j,r,i) recover(col[j]);
		s[dep]=0;
	}
	recover(C);	
	return false;
}

(叫 D a n c i n g    L i n k s    X Dancing\ \ Links\ \ X Dancing  Links  X是因为搜索过程在各行中跳)
可以通过【模板】舞蹈链(DLX)了。
D L X DLX DLX的复杂度为 O ( c n ) O(c^n) O(cn),其中 n n n为矩阵中 1 1 1的个数,而 c c c是一个十分接近于 1 1 1的常数,所以一般可以通过大部分题目。
不过该算法还有一个难点,将题目要求抽象成集合与矩阵。
我们回到数独这道题本身上来。
i i i j j j w w w ( i , j , w ) (i,j,w) (i,j,w)填一个数 k k k,因为 w w w可以由 i , j i,j i,j确定,所以我们只需要 i × j × k i\times j\times k i×j×k 9 × 9 × 9 = 729 9\times 9\times 9=729 9×9×9=729行(个集合)。
而这个操作又会引起哪些扰动呢。
会使 ( i , j ) (i,j) (i,j)位置被占
会使 i i i k k k这个数无法使用。
会使 j j j k k k这个数无法使用。
会使 w w w k k k这个数无法使用。
每种都需要 81 81 81列来存储,所以共 81 × 4 = 324 81\times 4=324 81×4=324列(集合大小)。
所以,我们成功将数独问题转化为一个 729 × 324 729\times 324 729×324的精确覆盖问题。
代码和模板的区别在于插入结点。

#include<bits/stdc++.h>
#define IT(i,A,x) for(int i=A[x];i!=x;i=A[i]) //好用
using namespace std;
const int N=1e5+5; //数组要开矩阵大小
int a,s[N],ans[105][105];
class Orthogonal_List{ //十字链表
	public:
		int n,m,tot,first[N],siz[N];
		int l[N],r[N],u[N],d[N];
		int col[N],row[N];
		void build(int R,int C)
		{
			n=R;m=C;
			for(int i=0;i<=C;i++) l[i]=i-1,r[i]=i+1,u[i]=d[i]=i;
			l[0]=C;r[C]=0;tot=C;
			memset(first,0,sizeof(first));
			memset(siz,0,sizeof(siz));
			return;
		}
		void insert(int R,int C)
		{
			row[++tot]=R;col[tot]=C;siz[C]++;
			u[tot]=C,d[tot]=d[C],u[d[C]]=tot,d[C]=tot;
			if(!first[R]) first[R]=l[tot]=r[tot]=tot;
			else l[tot]=first[R],r[tot]=r[first[R]],l[r[first[R]]]=tot,r[first[R]]=tot;
			return;
		}
		void remove(int C)
		{
			l[r[C]]=l[C];r[l[C]]=r[C];
			IT(i,d,C) IT(j,r,i) u[d[j]]=u[j],d[u[j]]=d[j],siz[col[j]]--;
			return;
		}
		void recover(int C)
		{
			IT(i,d,C) IT(j,r,i) u[d[j]]=d[u[j]]=j,siz[col[j]]++;
			l[r[C]]=r[l[C]]=C;
			return;
		}
		bool dance(int dep)
		{
			int C=r[0];
			if(!C) {for(int i=1,x,y,z;i<dep;i++) {x=((s[i]-1)/9)/9+1;y=((s[i]-1)/9)%9+1;z=(s[i]-1)%9+1;ans[x][y]=z;};return true;}
			IT(i,r,0) if(siz[i]<siz[C]) C=i;
			remove(C);
			IT(i,d,C)
			{
				s[dep]=row[i];
				IT(j,l,i) remove(col[j]); //这里用两个IT(j,r,i)就会错,其他都行,玄学。。。
				if(dance(dep+1)) return true;
				IT(j,r,i) recover(col[j]);
				s[dep]=0;
			}
			recover(C);
			return false;
		}
}lis;
char ch[100];
int main()
{
	while(scanf("%s",ch+1)&&ch[1]!='e')
	{
		lis.build(729,324); //建图
		for(int i=1;i<=9;i++)
		{
			for(int j=1;j<=9;j++)
			{
				a=ch[(i-1)*9+j]=='.'?0:ch[(i-1)*9+j]-'0';
				for(int k=1,l;k<=9;k++)
				{
					if(a-k&&a) continue; //其实就是有值就只填a,为空就每个都填
					l=((i-1)*9+(j-1))*9+k; //行
					lis.insert(l,(i-1)*9+j); //i行j列
					lis.insert(l,81+(i-1)*9+k); //i行k数
					lis.insert(l,162+(j-1)*9+k); //j列k数
					lis.insert(l,243+(((i-1)/3)*3+(j-1)/3)*9+k); //w宫k数
				}
			}
		}
		lis.dance(1); //你你你你要跳舞吗
		for(int i=1;i<=9;i++) for(int j=1;j<=9;j++) putchar(ans[i][j]+'0');putchar('\n');
	}
	return 0;
}

完结撒花。
总结:搜索是有极限的,所以我直接摆烂啦。
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值