搜索题选做

联赛将近,而我连暴力都会 FST ,这样下去联赛就要凉凉了。

1 . 【luogu P1120】小木棍

挣扎了 6 发才过,几乎是对着题解抄了一遍。可以说我是真的不会搜索剪枝了。

  • 题意

原来有一些长度相同的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过 50 50 50,总数 n ≤ 65 n \le 65 n65

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

  • 思路

先写出暴力搜索,然后考虑剪枝,在保证正确性的情况下能剪就剪。

  1. 枚举长度和的因数作为长棍原长。
  2. 每次要选木棍拼接的时候,把木棍按从大到小的顺序遍历。
  3. 在拼同一根长棍的时候,木棍从长到短选,也就是记一个 l s t lst lst ,然后从 l s t lst lst 开始遍历。
  4. 可以用桶来存木棍,这样不仅方便从大到小选取,还可以减少 dfs 中的循环大小。
  5. 重要:假如现在新开了一根长棍,选了最长的木棍拼上去,但是后面没有成功,回溯了,那么比他小的都不用再搜,直接回溯就好了。很显然,他现在不行,以后肯定不可能行。
  6. 重要:假如接上现在的木棍恰好拼成了一根完整的长棍,但是后来没有成功,那么也可以直接回溯。

后两种剪枝我并没有想到。

p.s. 剪枝的一般方法:

  1. 优化搜索顺序(2)
  2. 排除等效冗余(3,4)
  3. 可行性剪枝(1,5,6)
  4. 最优性剪枝
  5. 记忆化搜索

比如这题就只用了前三种。真的暴力跑不过去的时候可以往这些方面思考,不过一般都是靠人类智慧来剪枝。

2 . 【luogu P1378】 油滴拓展

  • 题意

在一个长方形框子里,最多有N(0≤N≤6)个相异的点,在其中任何一个点上放一个很小的油滴,那么这个油滴会一直扩展,直到接触到其他油滴或者框子的边界。必须等一个油滴扩展完毕才能放置下一个油滴。那么应该按照怎样的顺序在这N个点上放置油滴,才能使放置完毕后所有油滴占据的总体积最大呢?(不同的油滴不会相互融合)

  • 思路

复杂度完全过得去,按题意模拟即可。

但我还是没有 1A ,可以说是菜的不行了。

注意一个油滴直接覆盖了另一个油滴的位置的情况。

3 . 【luogu P1312】 Mayan 游戏

  • 题意

题面有点长还有图

Mayan puzzle是最近流行起来的一个游戏。游戏界面是一个 7 行 5 列的棋盘,上面堆放着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上。游戏通关是指在规定的步数内消除所有的方块,消除方块的规则如下:

1 、每步移动可以且仅可以沿横向(即向左或向右)拖动某一方块一格:当拖动这一方块时,如果拖动后到达的位置(以下称目标位置)也有方块,那么这两个方块将交换位置;如果目标位置上没有方块,那么被拖动的方块将从原来的竖列中抽出,并从目标位置上掉落。

2 、任一时刻,如果在一横行或者竖列上有连续三个或者三个以上相同颜色的方块,则它们将立即被消除

注意:

a) 如果同时有多组方块满足消除条件,几组方块会同时被消除。

b) 当出现行和列都满足消除条件且行列共享某个方块时,行和列上满足消除条件的所有方块会被同时消除

3 、方块消除之后,消除位置之上的方块将掉落,掉落后可能会引起新的方块消除。注意:掉落的过程中将不会有方块的消除。

给出最大通关步数 n ≤ 5 n\le 5 n5 ,在步数限制内通关不了就输出 − 1 -1 1

  • 思路

其实与搜索没有太大的关系,也不用怎么剪枝,因为状态数本来就少。

所以就是一个模拟题,码农题。还记得某次模拟赛有一个模拟扫雷交互的题,和此题类似。想要做对这种题就必须要老老实实模拟,把一个独立的过程装在一个函数里。比如这题的消除和下落是一个循环的过程,就把他单独拎出来写一个函数。

剪枝的话,我想到下面几个:

  1. 记录每一列的 s i z siz siz ,每次遍历只到 s i z siz siz 为止(或者到 0 为止)。
  2. 每次操作,用数组存下移动过的格子位置,那么就只有移动过的需要判断能否消掉。后面每一次下落之后也都同理,只需要判断移动过的位置能否被消掉。
  3. 对于两个相同颜色的格子,显然不交换
  4. 对于左移操作,假如左边有东西,那么显然可以用左边的格子右移来代替,字典序更优,所以除非左边是空格否则不左移。

如此看来,假如一次移动得很多,那么相应的他减少的格子应该也不少;而假如消掉的很少,那么我需要判断移动和消除的也会很少。所以复杂度大概得到了保证(雾)。

p.s. 因为这题说的是恰好 n n n 步结束游戏,所以第 3 3 3 个剪枝貌似不是很行,luogu 很多人乐此不疲地 hack 这种剪枝。所以去掉第 3 3 3 个剪枝就好了,其实也可以过。

4 . 【luogu P1441】 砝码称重

  • 题意

现有n个砝码,重量分别为a1,a2,a3,……,an,在去掉m个砝码后,问最多能称量出多少不同的重量(不包括0)。

请注意,砝码只能放在其中一边。

  • 思路

dfs 之后 bitset 做 DP 。可以把选 m 个不要换成选 n-m 个要,然后开 n 个 bitset 存搜索中每一层的 DP 状态,可以大大减小常数。

5 . 【luogu P1379】 八数码难题

  • 题意

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

  • 思路

状态数 9 ! 9! 9! 少得可怜,就算把所有状态全都搜一遍也没有问题。所以考虑记忆化搜索,也可以直接 bfs ,然后用康托展开去重。没有难度。

6 . 【luogu P1242】 新汉诺塔

  • 题意

规则与一般汉诺塔一样,但是有初始状态和目标状态。求达到目标状态的最小步数,并输出方案。

  • 思路

状态数太多,没办法暴力搜索。题解里有一种贪心从大到小把盘子移到目标位置,然后被 hack 了,貌似要用到一些随机算法。

我不是很会,不写了不写了。

7 . 【香蕉OI 11.1】 恶狼

做完了简单题,进入正题。

  • 题意

一个 N ∗ N N*N NN 的矩阵,每个点两个权值 a i , j , e i , j a_{i,j},e_{i,j} ai,j,ei,j ,每一个联通区域的权值为 ∏ a i , j x e i , j \prod a_{i,j}x^{e_{i,j}} ai,jxei,j ,现在你需要找出所有块数为 N N N 的联通区域,保证连通区域至少有一块在左边界上,至少有一块在上边界上,并计算他们的权值之和,输出 0 0 0 M M M 次项的系数。

N ≤ 15 , M ≤ 300 N\le 15, M\le 300 N15,M300

  • 思路

这道题出题人有一个非常巧妙的思路,但是我至今不知道该如何实现。他说,“至少有两块分别在左边界和上边界上”,相当于先搜出大小为 n 的联通区域的形状,然后向左上靠,直到靠到左边界和上边界为止。

假如这么做的话,只要暴力搜出形状,再 O ( n ) O(n) O(n) 统计答案就好了。

下面的做法其实并没有用到上面那个转化。

首先枚举联通区域的右下角,右下角是指最靠下的那些块中最靠右的那一个,而不是严格最右最下。然后从右下角开始搜索联通区域。

事实上这样做的复杂度瓶颈在于判重,因为假如想要一个哈希表来判重,空间复杂度会非常大。然后题解又给出了一种避免判重的神仙做法,用规定顺序的方法避免重复计算同一种方案。

把与连通区域相邻但是还没有选入联通区域的块存放在一个队列里,每次选取一个节点进入连通区域,把与他相邻的还没有进入队列的节点按顺序加入队列。然后选下一个点加入联通区域时,下一个点必须要排在所有已选的块的后面。

如此规定顺序之后,可以证明,对于每一种联通区域,他的各个块被加入的顺序也是只有一种的。所以可以知道无需再判重,只要在最后统计答案即可。

最后再加一个小剪枝,在距离左边界和上边界过远,不可能靠在左边界和上边界上的时候直接回溯。

p.s.判重的一般方法:

  1. 哈希
  2. 规定搜索顺序(重要)
  • 代码

写暴力也是要技巧的。想起考场上 200+ 行 10 分,说多了都是泪。

#include<bits/stdc++.h>
using namespace std;
const int dx[4] = {1, -1, 0, 0};
const int dy[4] = {0, 0, 1, -1};
const int N = 15 + 1, M = 300 + 10, mod = 1e9 + 7;
int n, m, a[N][N], e[N][N];
short sx, sy, qx[N*N], qy[N*N];
int ans[M];
bool inq[N][N];

template<class T>inline void read(T &x){
	x = 0; bool fl = 0; char c = getchar();
	while (!isdigit(c)){if (c == '-') fl = 1; c = getchar();}
	while (isdigit(c)){x = (x<<3)+(x<<1)+c-'0'; c = getchar();}
	if (fl) x = -x;
}

int add(int &x, int y){x += y; if (x >= mod) x -= mod;}

bool ok(short x, short y){
	if (x < 1 || x > n || y < 1 || y > n) return false;
	if (x < sx || (x == sx && y <= sy)) return true;
	return false;
}

void dfs(short stp, short x, short y, short now, short qn, short minx, short miny, int prod, int sum)
{
	if (minx+miny-2 > stp) return;
	if (stp == 0){
		add(ans[sum], prod);
		return;
	}
	int preqn = qn;
	for (int k = 0; k < 4; ++ k){
		int nx = x+dx[k], ny = y+dy[k];
		if (!ok(nx, ny) || inq[nx][ny]) continue;
		inq[nx][ny] = 1;
		qx[++qn] = nx; qy[qn] = ny;
	}
	for (int i = now+1; i <= qn; ++ i)
		dfs(stp-1, qx[i], qy[i], i, qn, min(minx, qx[i]), min(miny, qy[i]), 1LL*prod*a[qx[i]][qy[i]]%mod, sum+e[qx[i]][qy[i]]);
	for (int i = preqn+1; i <= qn; ++ i)
		inq[qx[i]][qy[i]] = 0;
}

int main()
{
	read(n); read(m);
	for (int i = 1; i <= n; ++ i)
		for (int j = 1; j <= n; ++ j)
			read(a[i][j]), read(e[i][j]);
	for (int i = 1; i <= n; ++ i)
		for (int j = 1; j <= n; ++ j){
			qx[1] = i; qy[1] = j;
			sx = i; sy = j;
			inq[i][j] = 1;
			dfs(n-1, i, j, 1, 1, i, j, a[i][j], e[i][j]);
			inq[i][j] = 0;
		}
	for (int i = 0; i <= m; ++ i)
		printf("%d ", ans[i]);
	puts("");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值