首页 > 其他分享 >Luogu P1784 数独 [ 模板 ] / P1074 靶形数独 题解 [ 蓝 ] [ 深搜 ] [ 剪枝 ] [ 卡常 ]

Luogu P1784 数独 [ 模板 ] / P1074 靶形数独 题解 [ 蓝 ] [ 深搜 ] [ 剪枝 ] [ 卡常 ]

时间:2024-06-10 20:23:18浏览次数:24  
标签:剪枝 15 int 题解 xvis Luogu ans return svis

数独模板靶形数独

卡了 2h ,再也不想写数独了。

思路

显然是对每个格子进行枚举,类似八皇后的方法去做,朴素方法是由 \((1,1)\) 到 \((9,9)\) 遍历过去。

优化

我们人在做数独时,会优先选择已填格数多的行、列、区域,这样可以保证尝试次数少。
同样,这一点在本题中也可以应用,但是有两种思路。

  1. 按照行里没填的格子的个数进行从小到大排序。
  2. 根据单个格子可能会出现的数字的数量进行从小到大排序。

目的只有一个:减少搜索树的大小。

这里采用第二种方法,
则计算这个格子可能的数字,就是竖向的数字、横向的数字、区域的数字以外的数字。
因此,我们将这三种数字开个 bitset ,然后分别一下,结果为 \(ans\) ,如果 \(ans[i]=0\) ,则证明可以填这个数。
bitset 可以用 状压 的方式优化,大幅减少常数。但我写的位运算版还没 bitset 版跑得快。

然后还要注意,如果搜到结果,直接 exit(0) 就好。

注意把 sort 改成 stable_sort ,因为尽可能要连续填。

于是就把数独过了。

数独代码:

第一种,bitset 版 ,跑洛谷的点很快,比位运算版要快:610ms

#include <bits/stdc++.h>
using namespace std;
int a[15][15];
bitset<15>xvis[15],yvis[15],svis[15];
int s(int x,int y)
{
	return ((x-1)/3*3+(y+2)/3);
}
int cg(int x,int y)
{
	bitset<15>ans;
	int res=0;
	ans=xvis[x]|yvis[y]|svis[s(x,y)];
	for(int i=1;i<=9;i++)if(ans[i]==0)res++;
	return res;
}
struct tile{
	int x,y,g;
};
vector<tile>vct;
bool cmp(tile a,tile b)
{
	return a.g<b.g;
}
void outp()
{
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			cout<<a[i][j]<<' ';
		}
		cout<<endl;
	}
}
void dfs(int now)
{
	if(now>=vct.size())
	{
		outp();
		exit(0);
	}
	int x=vct[now].x,y=vct[now].y;
	bitset<15>ans;
	ans=xvis[x]|yvis[y]|svis[s(x,y)];
	for(int i=1;i<=9;i++)
	{
		if(ans[i]==0)
		{
			svis[s(x,y)][i]=1;
			xvis[x][i]=1;
			yvis[y][i]=1;		
			a[x][y]=i;
			dfs(now+1);
			svis[s(x,y)][i]=0;
			xvis[x][i]=0;
			yvis[y][i]=0;		
			a[x][y]=0;			
		}
	}
}
int main()
{
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			cin>>a[i][j];
			if(a[i][j]!=0)
			{
				svis[s(i,j)][a[i][j]]=1;
				xvis[i][a[i][j]]=1;
				yvis[j][a[i][j]]=1;	
			}
		}
	}
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			if(a[i][j]!=0)continue;
			tile tmp;
			tmp.x=i,tmp.y=j;
			tmp.g=cg(i,j);
			if(tmp.g!=0)vct.push_back(tmp);
		}
	}
	stable_sort(vct.begin(),vct.end(),cmp);
	dfs(0);
	return 0;
}

第二种代码,位运算版,实际比 bitset 慢:3.28s

#include <bits/stdc++.h>
using namespace std;
int a[15][15];
int xvis[15],yvis[15],svis[15];
int s(int x,int y)
{
	return ((x-1)/3*3+(y+2)/3);
}
int cg(int x,int y)
{
	int ans;
	int res=0;
	ans=xvis[x]|yvis[y]|svis[s(x,y)];
	for(int i=1;i<=9;i++)if(ans>>i==0)res++;
	return res;
}
struct tile{
	int x,y,g;
};
vector<tile>vct;
bool cmp(tile a,tile b)
{
	return a.g<b.g;
}
void outp()
{
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			cout<<a[i][j]<<' ';
		}
		cout<<endl;
	}
}
void dfs(int now)
{
	if(now>=vct.size())
	{
		outp();
		exit(0);
	}
	int x=vct[now].x,y=vct[now].y;
	int tmps=s(x,y);
	int ans=xvis[x]|yvis[y]|svis[tmps];
	for(int i=1;i<=9;i++)
	{
		if(((ans>>i)&1)==0)
		{
			xvis[x]|=(1<<i);
			yvis[y]|=(1<<i);
			svis[tmps]|=(1<<i);		
			a[x][y]=i;
			dfs(now+1);
			xvis[x]=(xvis[x]^(1<<i));
			yvis[y]=(yvis[y]^(1<<i));		
			svis[tmps]=(svis[tmps]^(1<<i));	
			a[x][y]=0;	
		}
	}
}
int main()
{
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			cin>>a[i][j];
			if(a[i][j]!=0)
			{
				svis[s(i,j)]|=1<<a[i][j];
				xvis[i]|=1<<a[i][j];
				yvis[j]|=1<<a[i][j];	
			}
		}
	}
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			if(a[i][j]!=0)continue;
			tile tmp;
			tmp.x=i,tmp.y=j;
			tmp.g=cg(i,j);
			vct.push_back(tmp);
		}
	}
	stable_sort(vct.begin(),vct.end(),cmp);
	dfs(0);
	return 0;
}

靶形数独:增加权重的数独

讲几个重要剪枝:

  1. 增加估价函数 \(g()\) ,计算方式为 :当前剩下的没填的格子数\(*\)当前没填的数字的和\(*9+\)先前加过的分 。当估价函数小于当前答案时,直接剪枝。( \(9\) 是一个乐观期望的值)
  2. 化 vector 为普通数组。
  3. 乱搞排序,先按能填的个数从小到大排,再按横坐标从右到左排。这个方法不适用所有题,只是针对 ccf 的数据。
  4. 位运算,这里的位运算是要比 bitset 快的。
  5. stable_sort 。

这里不能搜到结果就直接结束,因为要使结果最大。

代码如下,本题没有完美的做法:1.77s,还是比较快的。

#include <bits/stdc++.h>
using namespace std;
int a[15][15],ans=-1,lst=81,cnt=0;
int xvis[15],yvis[15],svis[15];
int w[15][15]={{},{0,6,6,6,6,6,6,6,6,6},{0,6,7,7,7,7,7,7,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,9,10,9,8,7,6},{0,6,7,8,9,9,9,8,7,6},{0,6,7,8,8,8,8,8,7,6},{0,6,7,7,7,7,7,7,7,6},{0,6,6,6,6,6,6,6,6,6}};
inline int s(int x,int y)
{
	return ((x-1)/3*3+(y+2)/3);
}
int cg(int x,int y)
{
	int ans;
	int res=0;
	ans=xvis[x]|yvis[y]|svis[s(x,y)];
	for(int i=1;i<=9;i++)if(ans>>i==0)res++;
	return res;
}
struct tile{
	int x,y,g;
};
tile vct[105];
bool cmp(tile a,tile b)
{
	if(a.g!=b.g)return a.g<b.g;
	return a.x>b.x;
}
void dfs(int now,int sw,int lsm)
{
	if(now>=cnt)
	{
		ans=max(ans,sw);
		return;
	}
	if(sw+9*lsm*lst<ans)return;
	int x=vct[now].x,y=vct[now].y;
	int tmps=s(x,y);
	int ans=xvis[x]|yvis[y]|svis[tmps];
	for(int i=9;i>=1;--i)
	{
		if(((ans>>i)&1)==0)
		{
			xvis[x]|=(1<<i);
			yvis[y]|=(1<<i);
			svis[tmps]|=(1<<i);		
			a[x][y]=i;
			--lst;
			dfs(now+1,sw+i*w[x][y],lsm-i);
			xvis[x]=(xvis[x]^(1<<i));
			yvis[y]=(yvis[y]^(1<<i));		
			svis[tmps]=(svis[tmps]^(1<<i));	
			a[x][y]=0;	
			++lst;		
		}
	}
}
int main()
{
	int lsm=405,prsum=0;
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			cin>>a[i][j];
			if(a[i][j]!=0)
			{
				svis[s(i,j)]|=1<<a[i][j];
				xvis[i]|=1<<a[i][j];
				yvis[j]|=1<<a[i][j];	
				lst--;
				lsm-=a[i][j];
				prsum+=w[i][j]*a[i][j];
			}
		}
	}
	for(int i=1;i<=9;i++)
	{
		for(int j=1;j<=9;j++)
		{
			if(a[i][j]!=0)continue;
			tile tmp;
			tmp.x=i,tmp.y=j;
			tmp.g=cg(i,j);
			vct[cnt++]=tmp;
		}
	}
	stable_sort(vct,vct+cnt,cmp);
	dfs(0,prsum,lsm);
	cout<<ans;
	return 0;
}

标签:剪枝,15,int,题解,xvis,Luogu,ans,return,svis
From: https://www.cnblogs.com/zhr0102/p/18240894

相关文章

  • [题解]P9432 [NAPC-#1] rStage5 - Hard Conveyors
    P9432[NAPC-#1]rStage5-HardConveyors题意简述给定一个\(N\)个节点的树形结构,其中有\(k\)个关键节点。接下来有\(q\)次询问,每次询问给定\(x,y\),请输出\(x\)到\(y\)至少经过一个关键点的最短路径。解题思路我们发现,这道题相当于让我们从\(x\)到\(y\)的简单路径上,额外扩展......
  • P10572 [JRKSJ R8] +1-1 题解
    样例给了我们一个很好的提示。观察样例中\(1\rightarrow4\)的路径,发现\(4\rightarrow5\)这条边走了两遍,再结合题目描述中不需要保证是简单路径的提示,我们发现:如果路径两侧分别是(\(\rightarrow\)(和)\(\rightarrow\))的话,那么中间不管怎么走都可以通过左右横跳来......
  • LibreOJ #10131. 「一本通 4.4 例 2」暗的连锁 题解 树上差分
    暗的连锁题目描述Dark是一张无向图,图中有N个节点和两类边,一类边被称为主要边,而另一类被称为附加边。Dark有N−1条主要边,并且Dark的任意两个节点之间都存在一条只由主要边构成的路径。另外,Dark还有M条附加边。你的任务是把Dark斩为不连通的两部分。一开始Da......
  • [题解]P9433 [NAPC-#1] Stage5 - Conveyors
    P9433[NAPC-#1]Stage5-Conveyors题意简述给定一个\(N\)个节点的树形结构,每条边有边权,树上有\(k\)个关键点。接下来有\(q\)次询问,每次询问给定\(x,y\)两点,请计算从\(x\)开始经过这\(k\)个关键点(可以重复经过)再到\(y\)的最短路程。解题思路我们可以发现,如果\(x\)与\(y\)都......
  • 牛客周赛 Round 46 题解 C++
    目录 A 乐奈吃冰B 素世喝茶C 爱音开灯D 小灯做题E 立希喂猫F 祥子拆团 A 乐奈吃冰#include<iostream>#include<cstring>#include<algorithm>#include<cmath>#include<queue>#include<set>#include<vector>#include<unordered_map>......
  • 斜率优化DP简单总结&&“土地购买”题解
    今天刚刷完了斜率优化DP,简单从头回顾一下。\[首先,能写出DP方程应该是最重要的,毕竟斜率只是用来优化的\]那么一个DP方程能用斜率优化,具备一种形式:\[f[i]+s1[i]+A[i]*B[j]=f[j]+s2[j]\]其中,f[i]表示所求值,(s1[i]、A[i])与(s2[j]、B[j])分别表示只与i或j有关的一个表达式(可以是只有常......
  • 算法 | 剪枝函数以及几种形式&回溯法和分支限界法的区别&算法特性&分支限界法的思想&
    whatis剪枝函数?是对该问题能否得到最优解或者可行解的约束限界函数:最优解约束函数:可行解回溯法和分支限界法的区别:异:回溯法分支限界法一次生成/扩展一个结点一次生成所有的孩子结点BFSDFS/最小耗费优先找到所有解找到最优解同:均需要定义解空间,解空间的组织结构一般......
  • Codeforces Round 837题解(A、B)
    A.HossamandCombinatorics\(|a_i-a_j|\)最大的就是最大值和最小值,注意要开longlong。intn;inta[N];voidsolve(){cin>>n;intmin_v=INF,max_v=0;for(inti=1;i<=n;i++){cin>>a[i];min_v=min(min_v,a[i......
  • CF1970F1 Playing Quidditch (Easy) 题解
    一道大模拟题。这道题可以用一个 map 记录球员及鬼飞球当时的坐标,用一个数组 a 记录是否有人进球,用另一个数组 b 记录每位球员是否有鬼飞球。当球员抓住鬼飞球后,鬼飞球跟着这个球员移动,直到这个球员投球。话不多说,直接上代码。MyCode:#include<bits/stdc++.h>#de......
  • 【题解】 [CSP-J 2019] 纪念品
    题目描述题目大意在\(T\)天内,有\(n\)种纪念品和初始的\(m\)元。可以得到每天每种纪念品的价格。每一天可以以当日价格买卖纪念品。特别的,当天卖出得到的钱可以当天买入,当日买入的纪念品也可以当日卖出。当然可以一直持有。但是,\(T\)天过后,手上不可以持有纪念品。思路......