第 3 章 深搜的剪枝技巧(待更新inginging)

在考场中,时常会遇到题目拿到没想法,就先来打个表或写个暴力来骗分。DFS就是一个骗分神器。如果能加上几道优化,或许会有意想不到的结果。

例1:数的划分

类型 DFS+枚举优化

题目

第 3 章 深搜的剪枝技巧(待更新inginging)_第1张图片

题解

$ 这题在爆搜的时候,可以给后面留下足够的空间。$
$ 什么意思呢?假设当前的值为 x,选择 x 之后要再选\ k-1 个,那么要保证\ yu \geq k*x,yu表示n剩下的部分,$
$ 因为后面还要选择k-1个大小至少为x的数字。$

代码

#include
using namespace std;
int n,m,ans;
void dfs(int sum,int lst,int k)
{
	if (k==1){++ans;return;}
	for (int i=lst;i*k<=sum;++i) dfs(sum-i,i,k-1);
}
int main()
{
	scanf("%d%d",&n,&m);
	dfs(n,1,m);printf("%d\n",ans);
	return 0;
}

例2:蛋糕

类型 DFS+预判

题目

第 3 章 深搜的剪枝技巧(待更新inginging)_第2张图片

题解

这 题 看 似 D F S 不 可 做 , 实 际 上 加 入 大 量 预 判 剪 枝 后 , 跑 得 飞 快 。 这题看似DFS不可做,实际上加入大量预判剪枝后,跑得飞快。 DFS

先 预 判 体 积 。 如 果 发 现 接 下 来 按 照 最 小 的 方 式 放 都 不 能 使 得 体 积 小 于   n   或 是 按 照 最 大 的 方 式 放 都 不 能 达 到   n   就 停 先预判体积。如果发现接下来按照最小的方式放都不能使得体积小于 \ n\ 或是按照最大的方式放都不能达到\ n\ 就停 使 n  n 

再 判 表 面 积 。 如 果 接 下 来 拿 到 最 小 的 表 面 积 都 没 比 当 前   a n s   更 优 秀 , 那 么 就 停 。 或 者 可 以 用 另 一 种 ( 详 见 代 码 ) 再判表面积。如果接下来拿到最小的表面积都没比当前\ ans\ 更优秀,那么就停。或者可以用另一种(详见代码)  ans 

还 有 就 是 枚 举 的 时 候 可 以 预 判   r   和   h   的 取 值 范 围 。 还有就是枚举的时候可以预判\ r\ 和\ h\ 的取值范围。  r  h 
上 界 : r m a x = min ⁡ { y u , 上 次 选 的   r } , h m a x = min ⁡ { y u / r , 上 次 选 的   h } 上界:r_{max}=\min \{\sqrt {yu},上次选的\ r\},h_{max}=\min \{yu/r,上次选的\ h\} rmax=min{yu  r}hmax=min{yu/r h}
下 界 : r m i n = h m i n = min ⁡ { 1 , h i } 下界:r_min=h_min=\min\{1,hi\} rmin=hmin=min{1hi}
其 中   y u   表 示 剩 余 的 体 积 ,   h i   表 示 剩 余 的 层 数 。 其中\ yu\ 表示剩余的体积,\ hi\ 表示剩余的层数。  yu  hi 

代码

#include
using namespace std;
typedef long long LL;
int n,m;LL ans=1e18,f1[25],f2[25];
void dfs(LL hi,LL yu,LL R,LL H,LL S)
{
	bool pd=hi==m;
	if (hi==1)
	{
		for (LL r=min(R,(LL)sqrt(yu));r>0;--r)
		{
			LL p=pd?r*r:0;LL h=yu/r/r;if (h>H) break;
			if (h*r*r==yu) ans=min(ans,S+p+(h*r<<1));
		}
		return;
	}
	if (yu>hi*H*R*R||yu<f1[hi]||S+max(yu/R<<1,f2[hi])>ans) return;
	/*如果接下来hi层都按照最大都放不满yu单位空间*/
	/*如果接下来按照最小方式放蛋糕体积都超过n*/
	/*f2是最少要得到的侧面积。对于一个蛋糕,不考虑上下表面,那么最小的方式肯定要使得r尽可能大 
	当前情况下,r最大取到R。假设后面层都取到R,那么几层可以看成一层,侧面积为yu/R*2 */
	for (LL r=min(R,(LL)sqrt(yu));r>=hi;--r)
	{
		LL p=pd?r*r:0;
		for (LL h=min(H,yu/(r*r));h>=hi;--h)
		{
			dfs(hi-1,yu-r*r*h,r-1,h-1,S+(h*r<<1)+p);
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;++i) f1[i]=f1[i-1]+i*i*i,f2[i]=f2[i-1]+i*i*2;
	dfs(m,n,sqrt(n),n,0);
	printf("%lld",ans==1e18?0:ans);
	return 0;
}

例3:小木棍

类型 DFS+预判+枚举优化

题目

第 3 章 深搜的剪枝技巧(待更新inginging)_第3张图片

题解

这 题 乍 一 看 还 以 为 是 贪 心 , 实 际 上 是 有 漏 洞 的 。 没 办 法 , 只 能 写 D F S 。 这题乍一看还以为是贪心,实际上是有漏洞的。没办法,只能写DFS。 DFS
但 是 n = 60 还 是 比 较 大 的 , 想 想 办 法 尽 可 能 优 化 一 下 。 但是n=60还是比较大的,想想办法尽可能优化一下。 n=60

枚举方式
如 果 懒 得 写 链 表 , 我 们 可 以 枚 举 长 度 , 而 不 是 枚 举 木 棍 ( 长 度 小 于 数 量 ) 如果懒得写链表,我们可以枚举长度,而不是枚举木棍(长度小于数量)

枚举方式
从 m i n ( 上 次 选 择 长 度 , 最 长 的 长 度 , 这 根 剩 余 的 长 度 ) 开 始 枚 举 。 从min(上次选择长度,最长的长度,这根剩余的长度)开始枚举。 min()

预判
如果当前这根剩下的部分小于有最短的木棍可以直接弹出。

枚举方式
枚 举 的 时 候 , 按 照 从 大 到 小 的 顺 序 枚举的时候,按照从大到小的顺序
如 果 所 剩 的 木 棍 可 以 凑 到 长 度 L e n , 那 么 跟 顺 序 肯 定 没 关 系 如果所剩的木棍可以凑到长度Len,那么跟顺序肯定没关系 Len
但 是 按 顺 序 可 以 避 免 重 复 但是按顺序可以避免重复

预判
假 设 我 们 上 一 次 刚 好 凑 出 一 根 木 棍 , 剩 余 木 棍 的 集 合 S , 找 到 了 最 长 的 木 棍 x , 且   x ∈ S 。 假设我们上一次刚好凑出一根木棍,剩余木棍的集合S,找到了最长的木棍 x,且\ x\in S。 Sx xS
而 选 择 这 根 木 棍 后 接 下 来 却 凑 不 出 一 根 木 棍 , 那 么 就 直 接 弹 出 。 而选择这根木棍后接下来却凑不出一根木棍,那么就直接弹出。
剩 余 木 棍 的 肯 定 不 能 和   x   凑 成 新 木 棍 。 剩余木棍的肯定不能和\ x\ 凑成新木棍。  x 
即 使 换 一 根 短 一 点 的 木 棍 可 以 凑 出 来 整 根 , 凑 出 后   x   肯 定 没 被 使 用 过 , 否 则 刚 才 就 用 了 。 即使换一根短一点的木棍可以凑出来整根,凑出后\ x\ 肯定没被使用过,否则刚才就用了。 使 x 使
x   和 刚 才 那 个 集 合 S 中 的 就 不 能 凑 成 了 , 现 在 的 集 合 比 S 还 要 小 , 更 不 可 能 凑 出 整 根 了 。 x \ 和刚才那个集合 S 中的就不能凑成了,现在的集合比 S 还要小,更不可能凑出整根了。 x SS

后 面 两 个 的 效 果 比 较 明 显 。 后面两个的效果比较明显。

代码

#include
using namespace std;
const int maxn=65;
int n,a[maxn],h[55],p[55],MX,MI,sum,Len,LON;
void dfs(int yu,int stp,int lst)
{
	if (stp==n) {printf("%d",Len);exit(0);};
	bool pd;if (pd=!yu) lst=min(yu=Len,MX);
	for (int i=lst;i>=MI;++h[i--])
	  if (h[i]--&&(dfs(yu-i,stp+1,min(i,yu-i)),true)&&pd) {++h[i];return;}
}
int main()
{
	scanf("%d",&n);MI=1e9;
	for (int i=1;i<=n;++i) scanf("%d",&a[i]),++p[a[i]],sum+=a[i],MX=max(MX,a[i]),MI=min(MI,a[i]);LON=sum>>1;
	for (Len=MX,memcpy(h,p,sizeof p);Len<=LON;++Len) if (sum%Len==0) {dfs(0,0,0);memcpy(h,p,sizeof p);}
	printf("%d\n",sum);return 0;
}

例4:Addition Chains

类型 DFS+预判

题目

第 3 章 深搜的剪枝技巧(待更新inginging)_第4张图片

题解

这 题 在 搜 索 过 程 中 , 我 们 可 以 提 前 预 判 , 如 果   当 前 长 度 l e n   +   g e t ( a [ l e n ] ) ≥   当 前 最 优 解 这题在搜索过程中,我们可以提前预判,如果\ 当前长度len\ +\ get(a[len]) \geq \ 当前最优解  len + get(a[len]) 
其 中 , g e t ( a [ l e n ] )   为 一 个 估 价 函 数 , 表 示 当 前 最 大 值 为   a [ l e n ]   的 情 况 下 , 只 是 还 需 几 步 使 得   a [ l e n ] = n 其中,get(a[len]) \ 为一个估价函数,表示当前最大值为\ a[len]\ 的情况下,只是还需几步使得\ a[len] = n get(a[len])  a[len] 使 a[len]=n
a [ l e n ]   的 增 长 速 度 最 快 是 每 次 扩 大   2   倍 。 假 如 有 一 个 最 小 的   k   满 足   2 k + a [ l e n ] ≥ n , 这 个   k   就 是   g e t ( a [ l e n ] ) a[len]\ 的增长速度最快是每次扩大\ 2\ 倍。假如有一个最小的\ k \ 满足\ 2^k+a[len] \geq n,这个\ k\ 就是\ get(a[len]) a[len]  2  k  2k+a[len]n k  get(a[len])
如 果   a [ l e n ] 连 达 到   n   都 做 不 到 , 那 么 就 更 别 提 与   n   相 等 了 。 如果\ a[len] 连达到\ n\ 都做不到,那么就更别提与\ n\ 相等了。  a[len] n  n 

其 次 还 可 以 在 枚 举 的 时 候 加 上 一 些 优 化 , 比 如 : 因 为 我 们 的   a   序 列 是 递 增 的 , 而 且 新 的 元 素 要 大 于 当 前 序 列 最 大 值 其次还可以在枚举的时候加上一些优化,比如:因为我们的\ a\ 序列是递增的,而且新的元素要大于当前序列最大值  a 
所 以 我 们 可 以 倒 着 枚 举   i   和   j   , 这 样 一 旦 发 现   a [ i ] + a [ j ] < = a [ l e n ]   那 么 就 可 以 b r e a k 了 , 接 下 来   j   是 递 减 的 , a [ i ] + a [ j ′ ] 一 定 小 于 a [ l e n ] 所以我们可以倒着枚举\ i\ 和\ j\ ,这样一旦发现\ a[i]+a[j]<=a[len]\ 那么就可以break了,接下来\ j\ 是递减的,a[i]+a[j']一定小于a[len]  i  j  a[i]+a[j]<=a[len] break j a[i]+a[j]a[len]

代码

#include
using namespace std;
int n,a[105],ans,b[105];
int get(int x){int s=0;while(x<n) ++s,x<<=1;return s;}
void dfs(int stp)
{
	if (stp+get(a[stp])>=ans) return;
	if (a[stp]==n) {ans=stp;for (int k=1;k<=stp;++k)b[k]=a[k];return;}
	for (int i=stp;i>=1;--i)
	  for (int j=stp;j>=i;--j)
	    if (a[i]+a[j]>a[stp])
	    {
			if (a[i]+a[j]>n) continue;
			a[stp+1]=a[i]+a[j];dfs(stp+1);
		}else break;
}
int main()
{
	a[1]=1;
	while(true)
	{
		scanf("%d",&n);
		if (! n) break;
		ans=1e9;
		dfs(1);
		for (int i=1;i<ans;++i)printf("%d ",b[i]);printf("%d\n",b[ans]);
	}
	return 0;
}

例5:weight

类型 DFS+枚举剪枝(贪心)

题目

第 3 章 深搜的剪枝技巧(待更新inginging)_第5张图片

题解

这 题 首 先 想 到 的 是 枚 举 每 一 个 a i 是 集 合 中 的 第 几 号 元 素 。 这题首先想到的是枚举每一个a_i是集合中的第几号元素。 ai
这 样 加 上 提 前 判 断 能 否 满 足 前 缀 和 和 后 缀 和 , 也 是 要 超 时 的 。 这样加上提前判断能否满足前缀和和后缀和,也是要超时的。

突 发 奇 想 : 因 为 所 有 元 素 都 是 正 整 数 , 所 以 前 缀 和 肯 定 递 增 , 后 缀 和 肯 定 递 减 。 突发奇想:因为所有元素都是正整数,所以前缀和肯定递增,后缀和肯定递减。
对 表 示 前 后 缀 和 的 序 列 S 排 序 , 对 于 最 小 的 一 个 前 缀 和 或 后 缀 和 , 它 不 是 第 一 个 元 素 , 就 是 最 后 一 个 元 素 。 对表示前后缀和的序列S排序,对于最小的一个前缀和或后缀和,它不是第一个元素,就是最后一个元素。 S
1证明

对 于 第 二 个 元 素 也 是 差 不 多 的 道 理 。 剩 下 的 都 可 以 以 此 类 推 。 对于第二个元素也是差不多的道理。剩下的都可以以此类推。

代码

#include
using namespace std;
const int maxn=1005,maxm=505;
int n,m,h[maxm+5],S[maxn<<1],s1[maxn],s2[maxn],Sum,b[maxn];
bool pd(int x){return x>=1&&x<=500&&h[x];}
void dfs(int p,int L,int R)
{
	if (L==R)
	{
		if (pd(Sum-s1[L-1]-s2[R+1]))
		{
			b[L]=Sum-s1[L-1]-s2[R+1];
			for (int i=1;i<n;++i) printf("%d ",b[i]);
			printf("%d\n",b[n]);exit(0);
		}
		return;
	}
	b[L]=S[p]-s1[L-1];
	if (pd(b[L]))
	{
		s1[L]=S[p];
		dfs(p+1,L+1,R);
		s1[L]=0;
	}
	b[L]=0;
	b[R]=S[p]-s2[R+1];
	if (pd(b[R]))
	{
		s2[R]=S[p];
		dfs(p+1,L,R-1);
		s2[R]=0;
	}
	b[R]=0;
}
int main()
{
	scanf("%d",&n);n<<=1;
	for (int i=1;i<=n;++i) scanf("%d",&S[i]);sort(S+1,S+n+1);Sum=S[n];
	scanf("%d",&m);n>>=1;
	for (int x,i=1;i<=m;++i) scanf("%d",&x),h[x]=1;
	dfs(1,1,n);
	return 0;
}

练习1:埃及分数

类型 DFS+预判

题目

第 3 章 深搜的剪枝技巧(待更新inginging)_第6张图片

题解

之前写过了,直接套链接

代码

和题解在一起

练习2:平板涂色

本蒟蒻的代码找不到问题,但是就是WA……

练习3:靶形数独

类型 DFS+枚举剪枝

题目

第 3 章 深搜的剪枝技巧(待更新inginging)_第7张图片

题解

这 题 在 0 的 个 数 比 较 多 的 时 候 可 能 会 超 时 。 这题在 0 的个数比较多的时候可能会超时。 0

但 是 经 过 观 察 , 我 们 可 以 先 枚 举 确 定 已 经 的 数 字 个 数 较 多 的 行 ( 列 也 是 同 理 的 ) 但是经过观察,我们可以先枚举确定已经的数字个数较多的行(列也是同理的)
因 为 这 样 子 我 们 可 以 减 少 肯 定 不 必 要 的 枚 举 。 因为这样子我们可以减少肯定不必要的枚举。

就 比 如 说 第 5 行 第 1 个 数 字 是 这 行 唯 一 一 个 空 位 , 必 定 填 3 。 若 完 全 按 照 顺 序 枚 举 , 那 么 第 1 、 2 、 3 行 的 第 1 个 位 置 都 可 能 被 枚 举 到 为 3 ( 很 显 然 第 1 列 不 可 能 存 在 任 何 一 个 已 知 数 字 为 3 , 因 为 这 个 3 已 经 可 以 通 过 推 理 确 定 在 第 5 行 第 1 个 ) 就比如说第 5 行第 1 个数字是这行唯一 一个空位,必定填 3。若完全按照顺序枚举,那么第 1、2、3 行的第 1 个位置都可能被枚举到为 3(很显然第 1 列不可能存在任何一个已知数字为 3,因为这个 3 已经可以通过推理确定在第 5 行第 1 个) 5131231313351
若 先 把 这 个 3 定 住 , 那 将 减 少 多 少 不 必 要 的 枚 举 啊 若先把这个 3 定住,那将减少多少不必要的枚举啊 3
本 思 路 来 自 洛 谷 题 解 本思路来自洛谷题解
https://www.luogu.org/problemnew/solution/P1074
其 中 还 有 很 多 很 好 的 想 法 , 本 蒟 蒻 功 力 不 足 , 暂 且 就 说 这 些 吧 。 其中还有很多很好的想法,本蒟蒻功力不足,暂且就说这些吧。

代码

#include
using namespace std;
const int f[6]={0,6,7,8,9,10};
int a[15][15],x[15][15],y[15][15],t[15][15],id[15][15],h[7],hx[15],ans=-1;
int nxt()
{
	int id=10;
	for (int i=1;i<=9;++i) if (hx[i]<9&&hx[i]>hx[id]) id=i;
	return id;
}
int g(int F)
{
	int sum=0;
	for (int i=1;i<=5;++i) sum+=h[i]*9*f[F];
	return sum;
}
void dfs(int xx,int yy,int s)
{
	if (yy>9) {xx=nxt();yy=1;}
	int F=min(min(10-xx,10-yy),min(xx,yy));
	if (ans>=s+g(F)) return;
	if (xx>9) {ans=s;return;}
	if (a[xx][yy]){dfs(xx,yy+1,s);return;}
	for (int i=1;i<=9;++i)
	{
		if (!x[xx][i]&&!y[yy][i]&&!t[id[xx][yy]][i])
		{
			x[xx][i]=y[yy][i]=t[id[xx][yy]][i]=1;
			a[xx][yy]=i;--h[F];++hx[xx];
			dfs(xx,yy+1,s+i*f[F]);
			x[xx][i]=y[yy][i]=t[id[xx][yy]][i]=0;
			a[xx][yy]=0;++h[F];--hx[xx];
		}
	}
}
int main()
{
	for (int k=0;k<3;++k)
	for (int t=0;t<3;++t)
	{
		int m=k*3+t+1;
		for (int i=1;i<=3;++i)
		for (int j=1;j<=3;++j) id[i+k*3][j+t*3]=m;
	}
	int S=0;
	h[1]=32;
	h[2]=24;
	h[3]=16;
	h[4]=8;
	h[5]=1;
	for (int i=1;i<=9;++i)
	 for (int j=1;j<=9;++j)
	 {
	 	 scanf("%d",&a[i][j]);
	 	 t[id[i][j]][a[i][j]]=1;
	 	 x[i][a[i][j]]=1;
	 	 y[j][a[i][j]]=1;
	 	 int F=min(min(10-i,10-j),min(i,j));
	 	 if (a[i][j]) S+=a[i][j]*f[F],--h[F],++hx[i];
	 }
	hx[10]=-1;
	dfs(nxt()!=10?nxt():1,1,S);
	printf("%d",ans);
	return 0;
}

  1. 既然写证明,就写详细点吧 我 们 先 对 一 开 始 的 S 序 列 排 序 我们先对一开始的S序列排序 S ↩︎

你可能感兴趣的:(2018,DFS,优化)