深搜的剪枝技巧

剪枝方法

1.优化搜索顺序

在不同的问题中,搜索树的各个层次、各个分支之间的顺序不是固定的,不同的搜索顺序会产生不同的搜索树形态,其规模大小也相差甚远。

2.排除等效冗余

在搜索过程中,若能判断从搜索树当前节点上沿某几条不同分支到达的子树是相同的,那么只需对其中一条分支执行搜索。

3.可行性剪枝

可行性剪枝也叫上下界剪枝,其是指在搜索过程中,及时对当前状态进行检查,若发现分支已无法到达递归边界,就执行回溯。

4.最优性剪枝

在最优化问题的搜索过程中,若当前花费的代价已超过当前搜索到的最优解,那么无论采取多么优秀的策略到达递归边界,都不可能更新答案,此时可以停止对当前分支的搜索进行回溯。

题目

p s ps ps:懒得放题解了,凑合看吧,zzz

LOJ #10018. 「一本通 1.3 例 1」数的划分

题目链接

题意:将整数 n n n分成 k k k份,且每份不能为空,问有多少种不同的分法

直接搜会 T T T,要想剪枝:

  1. 每次搜索都从上一个搜到的数开始搜,去重
  2. 剩余未被划分的数不能小于剩余划分的次数

借大佬说的话:

因为保证没有重复方案,就必须保证枚举的结果递增(或者递减)。假设当前选择的数为 x x x,那么后面所有的数必须 ≥ x ≥x x,如果此时让剩下的数都 = x =x =x,还比 n n n小,那么剩下的几次递归其实都是无用的,这就是这一题的可行性剪枝。

#include 
#include 
#include 
using namespace std;

inline int read() {
	char c = getchar();
	int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
	return x * f;
}

int n, k, ans;

void dfs(int last, int cnt, int sum) {
	if(sum > n) return;
	if(cnt == k) {
		if(sum == n) ans++;
		return;
	}
	for(int i = last; sum + i * (k - cnt) <= n; i++) {
		dfs(i, cnt + 1, sum + i);
	}
}

int main() {
	n = read(), k = read();
	dfs(1, 0, 0);
	cout << ans << '\n';
	return 0;
}

LOJ #10019. 「一本通 1.3 例 2」生日蛋糕

题目链接

#include  
#include 
#include 
#include 
#include 
using namespace std;

const int A = 30;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n, m;
int minv[A], mins[A], ans = inf, h[A], r[A], s = 0, v = 0;

void dfs(int dep) {
	if(!dep) {
		if(v == n) ans = min(ans, s);
		return;
	}
	for(r[dep] = min((int)sqrt(n - v), r[dep + 1] - 1); r[dep] >= dep; r[dep]--)
		for(h[dep] = min((int)((double)(n-v) / r[dep] / r[dep]), h[dep + 1] - 1); h[dep] >= dep; h[dep]--) {
			if (v + minv[dep - 1] > n) continue;
			if (s + mins[dep - 1] > ans) continue;
			if (s + (double)2 * (n - v) / r[dep] > ans) continue;
			if(dep == m) s += r[dep] * r[dep];
			s += 2 * r[dep] * h[dep];
			v += r[dep] * r[dep] * h[dep];
			dfs(dep - 1);
			if (dep == m) s -= r[dep] * r[dep];
			s -= 2 * r[dep] * h[dep];
			v -= r[dep] * r[dep] * h[dep];
		}
}

int main() {
	n = read(), m = read();
	minv[0] = mins[0] = 0;
	for(int i = 1; i <= m; i++) {
		minv[i] = minv[i - 1] + i * i * i;
		mins[i] = mins[i - 1] + i * i;
	}
	h[m + 1] = r[m + 1] = inf;
	dfs(m);
	cout << ans << "\n";
	return 0;
}

LOJ #10020. 「一本通 1.3 例 3」小木棍

题目链接

#include 
#include 
#include 
#include 
using namespace std;

inline int read() {
	char c = getchar();
	int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
	return x * f;
}

const int N = 101;

int n, a[N], sum, len, maxn, vis[N], cnt, ll, tot;


bool dfs(int now, int lo, int last) {
	if(now == tot + 1 && lo == len) return true;
	if(lo == len) lo = 0, last = 0;
	for(int i = last + 1; i <= tot; i++) {
		if(vis[i]) continue;
		if(a[i] + lo > len) continue;
		vis[i] = 1;
		if(dfs(now + 1, lo + a[i], i)) return true;
		vis[i] = 0;
		if(lo == 0 || lo + a[i] == len) return false;
		while(a[i] == a[i + 1]) i++;
	}
	return false;
}

bool cmp(int a, int b) {
	return a > b;
}

int main() {
	n = read();
	sum = 0, maxn = 0;
	for(int i = 1; i <= n; i++) {
		int x = read();
		if(x <= 50) {
			a[++tot] = x;
			maxn = max(maxn, a[tot]);
			sum += a[tot];
		}
	}
	stable_sort(a + 1, a + 1 + tot, cmp);
//	cout << sum << '\n';
//	for(int i = 1; i <= tot; i++) cout << a[i] << ' '; 
	for(len = maxn; len <= sum; len++) {
		if(sum % len) continue;
		if(dfs(1, 0, 0)) {
			cout << len;
			return 0;
		}
	}
	cout << sum << '\n';
	return 0;
}

LOJ #10021. 「一本通 1.3 例 4」Addition Chains

题目链接

#include 
#include 
#include 
#include 
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int a[A], ans[A], minn, n;

void dfs(int now) {
	if(now > minn || a[now] > n) return;
	if(a[now] == n) {
		if(now > minn) return;
		minn = now;
		for(int i = 1; i <= now; i++) ans[i] = a[i];
		return;
	}
	for(int i = now; i >= 1; i--) {
		if(a[now] + a[i] > n) continue;
		a[now + 1] = a[now] + a[i];
		dfs(now + 1);
		a[now + 1] = 0;
	}
	return;
}

int main() {
	while(scanf("%d", &n) != EOF) {
		if(n == 0) break;
		minn = inf, a[1] = 1;
		dfs(1);
		for(int i = 1; i <= minn; i++) cout << ans[i] << " ";
		puts("");
	}
	return 0;
}

LOJ #10249. 「一本通 1.3 例 5」weight

题目链接

神奇的搜索对象

#include 
#include 
#include 
#include 
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int x, n, m, p, sum[A], a[A];
bool vis[A];

bool dfs(int cnt, int l, int r, int lsum, int rsum) {
	if(l == r) {
		if(!vis[sum[cnt] - lsum] && !vis[sum[cnt] - rsum]) return 0;
		a[l] = sum[n * 2] - lsum - rsum;
		if(a[l] < 1 || a[l] > 500) return 0;
		for(int i = 1; i <= cnt; i++) cout << a[i] << " ";
		return 1;
	}
	if(vis[sum[cnt] - lsum]) {
		a[l] = sum[cnt] - lsum;
		if(dfs(cnt + 1, l + 1, r, sum[cnt], rsum)) return 1;
	}
	if(vis[sum[cnt] - rsum]) {
		a[r] = sum[cnt] - rsum;
		if(dfs(cnt + 1, l, r - 1, lsum, sum[cnt])) return 1;
	}
	return 0;
}

int main() {
	n = read();
	for(int i = 1; i <= 2 * n; i++) sum[i] = read();
	m = read();
	for(int i = 1; i <= m; i++) x = read(), vis[x] = 1;
	stable_sort(sum + 1, sum + 1 + n * 2);
	dfs(1, 1, n, 0, 0);
}

LOJ #10022. 「一本通 1.3 练习 1」埃及分数

题目链接

#include 
#include 
#include 
#include 
#define int long long
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar();
	int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n, m, cur[1005], ans[1005], mn;

void dfs(int x, int a, int b, int n, int last) {
	if (x == n) {
		if(b % a) return;
		b /= a; cur[x] = b;
		if(b < mn) {
			mn = b;
			for(int i = 1; i <= n; i++) ans[i] = cur[i];
		}
		return;
	}
	last++;
	while(a * last <= b && last < mn) last++;
	for(; last < mn; last++) {
		if(a * last >= b * (n - x + 1)) break;
		cur[x] = last;
		dfs(x + 1, a * last - b, b * last, n, last);
	}
	return;
}

signed main() {
	n = read(), m = read();
	int i = 1; mn = 1e9;
	while(1) {
		dfs(1, n, m, i, 0);
		if(mn < 1e9) break;
		i++;
	}
	for(int j = 1; j <= i; j++) cout << ans[j] << " ";
	return 0;
}

LOJ #10023. 「一本通 1.3 练习 2」平板涂色

题目链接

#include 
#include 
#include 
#include 
#include 
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar(); int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

int n;
vector <int> on[20];
struct node { int x1, y1, x2, y2, col; } a[A];
int ans = inf, vis[A];

bool check(int k) {
	int siz = (int)on[k].size();
	for(int i = 0; i < siz; i++) if(!vis[on[k][i]]) return 0;
	return 1;
}

void dfs(int tot, int fin, int col) {
	if(fin == n) {
		ans = min(ans, tot);
		return;
	}
	if(tot >= ans) return;
	for(int i = 1; i <= n; i++) {
		if(!vis[i] && check(i)) {
			vis[i] = 1;
			dfs(tot + (col != a[i].col), fin + 1, a[i].col);
			vis[i] = 0;
		}
	}
}

int main() {
	n = read();
	for(int i = 1; i <= n; i++) 
		a[i].x1 = read(), a[i].y1 = read(), a[i].x2 = read(), a[i].y2 = read(), a[i].col = read();
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
			if(i != j && a[i].x1 == a[j].x2 && a[i].y2 >= a[j].y1 && a[i].y1 <= a[j].y2)
				on[i].push_back(j);
//	for(int i = 1; i <= n; i++) {
//		int t = on[i].size(); cout << "size: " <<  t << "\n";
//		for(int j = 0; j < t; j++) cout << on[i][j] <<" "; puts("");
//	}
	dfs(0, 0, 0);
	cout << ans << '\n'; return 0;
}

LOJ #2591. 「NOIP2009」靶形数独

题目链接

特别想说一下这道题,因为真的特!别!好!玩!

数独都懂吧

1. 1. 1.每一行不能有一样的数字
2. 2. 2.每一列不能有一样的数字
3. 3. 3.每个 3 ∗ 3 3*3 33的九宫格不能有一样的数字

首先上来的感觉就是要搜索,那么,搜索的话,我们要想一下怎么搜,这里可以直接记录某一行某个数是否出现,某一列某个数是否出现,某个九宫格内某个数是否出现,前两个好弄,直接循环的时候记录就好了,最后一个怎么办?

而且怎么计算格子的分数呢??一个一个乘?

所以,我们要解决的问题是:怎么计算格子的分数,怎么判断是第几个九宫格

  1. 一开始在想怎么计算这个格子的分数是多少,后来决定干脆直接开个 s c o r e score score数组存下来得了,算的时候一乘就完事儿了
const int score[10][10] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{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}, };
  1. 我的方法是,硬判断,就像下面这样(为了美观一些不需要判断的条件我都打上了,反正影响也不大)
int pd(int i, int j) {
	if(i <= 3 && j <= 3) return 1;
	if(i <= 3 && j <= 6) return 2;
	if(i <= 3 && j <= 9) return 3;
	if(i <= 6 && j <= 3) return 4;
	if(i <= 6 && j <= 6) return 5;
	if(i <= 6 && j <= 9) return 6;
	if(i <= 9 && j <= 3) return 7;
	if(i <= 9 && j <= 6) return 8;
	if(i <= 9 && j <= 9) return 9;
}

然后我们就可以一行一行的搜索,填完一行再填下一行,然后就可以得到六十分的好成绩

#include 
#include 
#include 
#include 
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar();
	int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

const int score[10][10] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{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}, };

int pd(int i, int j) {
	if(i <= 3 && j <= 3) return 1;
	if(i <= 3 && j <= 6) return 2;
	if(i <= 3 && j <= 9) return 3;
	if(i <= 6 && j <= 3) return 4;
	if(i <= 6 && j <= 6) return 5;
	if(i <= 6 && j <= 9) return 6;
	if(i <= 9 && j <= 3) return 7;
	if(i <= 9 && j <= 6) return 8;
	if(i <= 9 && j <= 9) return 9;
}

int a[11][11], hang[11][11], lie[11][11], sma[11][11];
int tot, ans = -10000;

void coun() {
	int now = 0;
	for(int i = 1; i <= 9; i++) 
		for(int j = 1; j <= 9; j++)
			now += a[i][j] * score[i][j];
	ans = max(ans, now);
	return;
}

void dfs(int ho, int li) { //行、列、这一行填了多少个 
	if(ho == 10) { coun(); return; }
	if(li == 10) dfs(ho + 1, 1);
	if(!a[ho][li]) {
		for(int i = 1; i <= 9; i++) {
			if(!hang[ho][i] && !lie[li][i] && !sma[pd(ho, li)][i]) {
				hang[ho][i] = lie[li][i] = sma[pd(ho, li)][i] = 1;
				a[ho][li] = i; dfs(ho, li + 1); a[ho][li] = 0;
				hang[ho][i] = lie[li][i] = sma[pd(ho, li)][i] = 0;
			}
		}
	}
	else dfs(ho, li + 1);
}

int main() {
	for(int i = 1; i <= 9; i++)
		for(int j = 1; j <= 9; j++) {
			a[i][j] = read();
			if(a[i][j]) {
				sma[pd(i, j)][a[i][j]] = 1;
				hang[i][a[i][j]] = 1;
				lie[j][a[i][j]] = 1;
			}
			else tot++;
		}
	dfs(1, 1);
	cout << ans << "\n";
	return 0;
}

怎么优化?

我不会优化呀!!怎么办!只能搜了……只见大佬说

从填数最多的一行开始填,这样要选择的数就少了,不合法的情况就可以省掉一些

然后就满分了 q w q qwq qwq

#include 
#include 
#include 
#include 
using namespace std;

const int A = 5e5 + 11;
const int B = 1e6 + 11;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;

inline int read() {
	char c = getchar();
	int x = 0, f = 1;
	for( ; !isdigit(c); c = getchar()) if(c == '-') f = -1;
	for( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
	return x * f;
}

const int score[10][10] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{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}, };

int pd(int i, int j) {
	if(i <= 3 && j <= 3) return 1;
	if(i <= 3 && j <= 6) return 2;
	if(i <= 3 && j <= 9) return 3;
	if(i <= 6 && j <= 3) return 4;
	if(i <= 6 && j <= 6) return 5;
	if(i <= 6 && j <= 9) return 6;
	if(i <= 9 && j <= 3) return 7;
	if(i <= 9 && j <= 6) return 8;
	if(i <= 9 && j <= 9) return 9;
}

int a[11][11], hang[11][11], lie[11][11], sma[11][11];
int tot, ans = -1;
struct node { int sum, line; } qwq[11];

bool cmp(node a, node b) {
	return a.sum < b.sum;
}

void coun() {
	int now = 0;
	for(int i = 1; i <= 9; i++) 
		for(int j = 1; j <= 9; j++)
			now += a[i][j] * score[i][j];
	ans = max(ans, now);
	return;
}

void dfs(int cnt, int ho, int li) { //行、列、这一行填了多少个 
	if(cnt == 10) { coun(); return; }
	if(li == 10) dfs(cnt + 1, qwq[cnt + 1].line, 1);
	if(!a[ho][li]) {
		for(int i = 1; i <= 9; i++) {
			if(!hang[ho][i] && !lie[li][i] && !sma[pd(ho, li)][i]) {
				hang[ho][i] = lie[li][i] = sma[pd(ho, li)][i] = 1;
				a[ho][li] = i; dfs(cnt, ho, li + 1); a[ho][li] = 0;
				hang[ho][i] = lie[li][i] = sma[pd(ho, li)][i] = 0;
			}
		}
	}
	else dfs(cnt, ho, li + 1);
}

int main() {
	for(int i = 1; i <= 9; i++) {
		tot = 0;
		for(int j = 1; j <= 9; j++) {
			a[i][j] = read();
			if(a[i][j]) {
				sma[pd(i, j)][a[i][j]] = 1;
				hang[i][a[i][j]] = 1;
				lie[j][a[i][j]] = 1;
			}
			else tot++;
			qwq[i].line = i, qwq[i].sum = tot;
		}
	}
	sort(qwq + 1, qwq + 1 + 9, cmp);
	dfs(1, qwq[1].line, 1);
	cout << ans; return 0;
}

你可能感兴趣的:(一本通提高篇)