Part 3.1 深度优先搜索

题单

P1219 [USACO1.5]八皇后 Checker Challenge

思路 :

根据题干,我们可以先放第一行元素,在放第二行的元素,……,最后放第n的元素。这样做的好处是,我们在搜索时可以只枚举列,而不用枚举行。然后我们在定义b[i]、c[i]、d[i]分别来记录第i列、第i个与主对角线平行的对角线、第i个与副对角线平行的对角线上是否有棋子;行是不需要记录的,因为我们是顺次把棋子放下来的。最后,我们用一个a数组来记录下路径就可以了。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 105;
int n, a[maxn], b[maxn], c[maxn], d[maxn], total;
void print ()  // 打印路径
{
	for (int i = 1; i <= n; i++) 
	{
		printf ("%d ", a[i]);
	}
	printf ("\n");
}
void dfs (int cur) 
{
	for (int j = 1; j <= n; j++) 
	{
		if (!b[j] && !c[cur + j] && !d[cur - j + n]) 
		{
			a[cur] = j;
			if (cur == n) 
			{
				total ++;
				if (total <= 3)
					print ();
				return;
			}
			b[j] = 1;
			c[cur + j] = 1;
			d[cur - j + n] = 1;
			dfs (cur + 1);
			b[j] = 0;
			c[cur + j] = 0;
			d[cur - j + n] = 0;
		}
	}
}
int main ()
{
	scanf ("%d", &n);
	dfs (1);
	printf ("%d", total);
	return 0;
}

收获

学会记录并打印路径
学会如何标记主副对角线

P1019 [NOIP2000 提高组] 单词接龙

思路

首先,为了dfs能够正常进行,我们需要找出可以实现拼接的且不同的字符串对(si, sj),并把他们的拼接(即重合)的最小长度记录在a[i][j]中。为什么是最小长度?因为我们的答案要的是拼接后的串的最大长度,故对于两个可实现拼接的字符串,其用于拼接的长度越短越好。然后就是我们发现每一个串可以用两次,那怎么办,vis[i]只能记录它是否被用过一次,其实并不然,我们只需把vis[i] == 0 改为vis[i] <= 1就可以实现表示其还没用完(而不是还没被用),那题干含说了两个用与拼接的的字符串不能有包含关系,这只需要aij!=si.length()&&aij!=sj.length (),就可以了。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 50;
string s[maxn];
int n, a[maxn][maxn], ans, vis[maxn];
char ch; 
int f (string x, string y) // 返回两个串i串末尾与j串首段的最小重叠长度
{
	int lenx = x.length ();
	for (int l = 1; l <= lenx; l++) //从1开始 
	{
		int flag = 1;
		for (int i = lenx - l, j = 0; i < lenx; i++, j++) 
		{
			if (x[i] != y[j]) 
			{
				flag = 0;
				break;
			}
		}
		if (flag == 1) 
		{
			return l;
		}
	}
	return 0;
}
void dfs (int cur, int ansi) 
{
	for (int i = 1; i <= n; i++) 
	{
		if (vis[i] <= 1 && a[cur][i] > 0 && a[cur][i] != s[cur].length () && a[cur][i] != s[i].length ()) // 这里是本题的重点 
		{
			vis[i] ++;
			dfs (i, ansi + s[i].length () - a[cur][i]); //容斥原理
			vis[i] --;
		}
	}
	//到这了,说明不可拼接了,更新一下ans
	ans = max (ansi, ans);
	return ;
}
int main ()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++) 
	{
		cin >> s[i];
	}
	cin >> ch;
	//初始化a[i][j]
	for (int i = 1; i <= n; i++) 
	{
		for (int j = 1; j <= n; j++) 
		{
			a[i][j] = f (s[i], s[j]);
			a[j][i] = f (s[j], s[i]);
		}
	}
	for (int i = 1; i <= n; i++) 
	{
		if (s[i][0] == ch) 
		{
			vis[i] ++;
			dfs (i, s[i].length ());
			vis[i] --;
		}
	}
	printf ("%d", ans);
	return 0;
}

收获

打破对vis数组的刻板用法
学会判断包含关系

P5194 [USACO05DEC]Scales S

思路

这个题n最多有1000个,这显然会超时,而这个题由不可以记忆化,故考虑搜索的剪枝。 首先我们想为啥会TLE,因为一个一个枚举太慢了,那我们应该去寻找一个方法使得使得一次可以加上许多个,于是考虑到可以使用前缀和pre[i]记录前i个砝码的总质量,具体的操作方法见代码说明。当然,如果目前加上去的砝码总质量超过了c,那么一定要return了,这也是一个必要的剪枝。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1005;
long long a[maxn], n, c, ans;
long long pre[maxn];
void dfs (int cur, long long x) //表示目前下标从后往前加上了的下标最小砝码的是cur,x表示目前的总重量,因为cur是递减的,所以可以做到不重不漏
{
	if (x > c) //超过最大称重的一定不合理
	{
		return;
	}
	if (x + pre[cur - 1] <= c) //如果前cur-1个可以全加上,那么直接全加上,然后更新答案
	{
		ans = max (ans, x + pre[cur - 1]);
		return;
	}
	//如果不可以。则先更新一下答案,在从前cur-1个中找一个放上去。
	ans = max (ans, x);
	for (int i = 1; i < cur; i++) 
	{
		dfs (i, x + a[i]);
	}
}
int main ()
{
	scanf ("%lld %lld", &n, &c);
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%lld", &a[i]);
		pre[i] = pre[i - 1] + a[i]; //前缀和数组
	}
	dfs (n + 1, 0);
	printf ("%lld", ans);
	return 0;
}

收获

知道了前缀和也可以用于剪枝
知道了一种让搜索做到不重不漏的新的搜索方法,详见dfs 函数

P1378 油滴扩展

思路

一个模拟题,但又一个特别需要注意的点,就是内切的情况,其半径要设为0!!!

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
int n, xii, yii, xi, yi, x[maxn], y[maxn], vis[maxn], temp[maxn];
double r[maxn], pi = 3.1415926535, ans = 2147483647;
int l (int i, int j) 
{
	int delx = x[i] - x[j], dely = y[i] - y[j];
	return delx * delx + dely * dely;
}
void dfs (int tot, int cur) 
{
	temp[tot] = cur;
	double li = min (abs (x[cur] - xi), abs (x[cur] - xii));
	double lii = min (abs (y[cur] - yi), abs (y[cur] - yii));
	r[tot] = min (li, lii);
	for (int i = 1; i <= tot - 1; i++) 
	{
		int id = temp[i];
		double ri = sqrt (1.0 * l (cur, id)) - r[i];
		if (ri > 0)
			r[tot] = min (r[tot], ri);
		else 
		{
			r[tot] = 0;
		}
	}
	if (tot == n) 
	{
		double ansi = 0;
		for (int i = 1; i <= n; i++) 
		{
			ansi += pi * r[i] * r[i];
		}
		ansi = abs ((xii - xi) * (yii - yi)) - ansi;
		ans = min (ansi, ans);
		return; 
	}
	for (int i = 1; i <= n; i++) 
	{
		if (!vis[i]) 
		{
			vis[i] = 1;
			dfs (tot + 1, i);
			vis[i] = 0;
		}
	}
	return;
}
int main ()
{
	scanf ("%d%d%d%d%d", &n, &xi, &yi, &xii, &yii);
	for (int i = 1; i <= n; i++) 
	{
		scanf ("%d %d", &x[i], &y[i]);
	}
	for (int i = 1; i <= n; i++)
	{
		vis[i] = 1;
		dfs (1, i);
		vis[i] = 0;	
	}
	printf ("%d", int (ans + 0.5));
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值