剪枝,形象得看,就是剪掉搜索树的分⽀,从⽽减⼩搜索树的规模,排除掉搜索树中没有必要的分⽀,优化时间复杂度。
在深度优先遍历中,有⼏种常⻅的剪枝⽅法
搜索策略:
[1, n]
个数放在k 个坑⾥⾯,使的坑⾥⾯的所有数的总和是n ;[1,2]
与[2,1]
是同⼀种分法,因此,应该是⼀种组合型枚举。针对每⼀个坑⾥⾯的数应该放谁的时候,应该从上⼀个坑⾥⾯的数开始枚举。#include
using namespace std;
int n, k;
int path, ret;
void dfs(int pos, int begin)
{
if (pos == k)
{
if (path == n) ret++;
return;
}
//可行性剪枝
//if (path + begin * (k-pos) > n) return;
for (int i = begin; i <= n; i++)
{
//可行性剪枝
if (path + i * (k-pos) > n) return;
path += i;
dfs(pos+1, i);
path -= i;
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
dfs(0, 1); //填的位置和开始的数
cout << ret << endl;
return 0;
}
搜索策略:依次处理每⼀只猫,对于每⼀只猫,我们都有两种处理⽅式:
#include
using namespace std;
const int N = 20;
int n, w;
int c[N];
int cnt; //统计车的数量
int s[N]; //统计每一辆车目前的总重
int ret = N;
bool cmp(int a, int b)
{
return a > b;
}
void dfs(int pos)
{
//最优性剪枝
if (cnt >= ret) return;
if (pos > n)
{
ret = cnt;
return;
}
//先安排在已有的车上
for (int i = 1; i <= cnt; i++)
{
//可行性剪枝
if (s[i] + c[pos] > w) continue;
s[i] += c[pos];
dfs(pos+1);
s[i] -= c[pos]; //恢复现场
}
//重开一辆
cnt++;
s[cnt] = c[pos];
dfs(pos+1);
//恢复现场
s[cnt] = 0;
cnt--;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> w;
for (int i = 1; i <= n; i++) cin >> c[i];
//优化搜索顺序
sort(c+1, c+n+1, cmp);
dfs(1);
cout << ret << endl;
return 0;
}
也是⼀种剪枝策略。
通过⼀个"备忘录",记录第⼀次搜索到的结果,当下⼀次搜索到这个状态时,直接在"备忘录"⾥⾯找结果。
记忆化搜索,有时也叫动态规划
在搜索的过程中,如果发现特别多完全相同的⼦问题,就可以添加⼀个备忘录,将搜索的结果放在备忘录中。下⼀次在搜索到这个状态时,直接在备忘录⾥⾯拿值
递归解法
class Solution {
public:
int dfs(int n)
{
if (n == 1 || n == 0) return n;
return dfs(n-1) + dfs(n-2);
}
int fib(int n) {
return dfs(n);
}
};
记忆化搜索
class Solution {
int f[35];
public:
int dfs(int n)
{
if (f[n] != -1) return f[n];
if (n == 1 || n == 0) return n;
f[n] = dfs(n-1) + dfs(n-2);
return f[n];
}
int fib(int n) {
memset(f, -1, sizeof f);
return dfs(n);
}
};
题⽬叙述的⾮常清楚,我们仅需按照「题⽬的要求」把「递归函数」写出来即可。但是,如果不做其余处理的话,结果会「超时」。因为我们递归的「深度」和「⼴度」都⾮常⼤。
通过把「递归展开图」画出来,我们发现,在递归过程中会遇到⼤量「⼀模⼀样」的问题
比如求w(20,20,20)
因此,可以在递归的过程中,把每次算出来的结果存在⼀张「备忘录」⾥⾯。等到下次递归进⼊「⼀模⼀样」的问题之后,就「不⽤傻乎乎的展开计算」,⽽是在「备忘录⾥⾯直接把结果拿出来」,起到⼤量剪枝的效果
#include
using namespace std;
typedef long long LL;
const int N = 25;
LL a, b, c;
LL f[N][N][N]; //备忘录
LL dfs(LL a, LL b, LL c)
{
if (a <= 0 || b <= 0 || c <= 0) return 1;
if (a > 20 || b > 20 || c > 20) return dfs(20, 20, 20);
if (f[a][b][c]) return f[a][b][c];
if (a < b && b < c) return f[a][b][c] = dfs(a, b, c-1) + dfs(a, b-1, c-1) - dfs(a, b-1, c);
else return f[a][b][c] = dfs(a-1, b, c) + dfs(a-1, b-1, c) + dfs(a-1, b, c-1) - dfs(a-1, b-1, c-1);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
while (cin >> a >> b >> c)
{
//不需要清空
if (a == -1 && b == -1 && c == -1) break;
printf("w(%lld, %lld, %lld) = %lld\n", a, b, c, dfs(a, b, c));
}
return 0;
}
⽤递归模拟整个游戏过程:dfs(x, y) 的结果可以由dfs((x + y) % p, (x + y + y) % p) 得到。
因为测试数据是多组的,并且模数都是p,再加上递归的过程中会递归的相同的问题,所以可以把递归改写成记忆化搜索。其中:
f[x][y] = 1
,表⽰cbw 赢;f[x][y] = 2
,表⽰zhouwc 赢;f[x][y] = 3
表⽰这个位置已经被访问过,如果没被修改成1 或者2 ,那就表明平局。#include
using namespace std;
const int N = 1e4 + 10;
int x, y, p;
char f[N][N]; //备忘录
char dfs(int x, int y)
{
if (f[x][y]) return f[x][y]; //剪枝
f[x][y] = '3'; //这个状态已经访问过了
if (x == 0) return f[x][y] = '1';
if (y == 0) return f[x][y] = '2';
return f[x][y] = dfs((x+y)%p, (x+y+y)%p);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int T; cin >> T >> p;
while (T--)
{
cin >> x >> y;
char ret = dfs(x, y);
if (ret == '1') cout << 1 << endl;
else if (ret == '2') cout << 2 << endl;
else cout << "error" << endl;
}
return 0;
}
暴⼒枚举:遍历整个矩阵,看看以当前位置为起点,最远能滑⾏多远的距离。在所有情况⾥⾯,取最⼤值即可。
如何求出以[i, j]
为起点的最⼤距离?
[i,y]
位置上下左右瞅⼀瞅,如果能滑过去,就看看以下⼀个位置为起点,最远能滑⾏多远的距离;#include
using namespace std;
const int N = 110;
int n, m;
int a[N][N];
int f[N][N]; //备忘录
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
int dfs(int i, int j)
{
if (f[i][j]) return f[i][j];
int len = 1;
//上下左右找
for (int k = 0; k < 4; k++)
{
int x = i + dx[k], y = j + dy[k];
if (x < 1 || x > n || y < 1 || y > m) continue;
if (a[i][j] <= a[x][y]) continue;
len = max(dfs(x, y)+1, len);
}
return f[i][j] = len;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j];
int ret = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
ret = max(ret, dfs(i, j));
cout << ret << endl;
return 0;
}