记忆化搜索,顾名思义吗,搜索一次记忆一次,功能
题目描述
楼梯有 NN 阶,上楼可以一步上一阶,也可以一步上二阶。
编一个程序,计算共有多少种不同的走法。
就是提高效率呗。
很容易看出就是个递推呗,斐波那契数列。那这道题数据很大,一次一次的递推会超限
没啥好说的就是高精的斐波那契,前面也有写到。
P1002 [NOIP2002 普及组] 过河卒 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1002
定义一个二维数组f(i,j)表示走到(i,j)的方案数,显然f(i,j)=f(i-1,j)+f(i,j-1)。
当卒从起点开始笔直地向下或笔直地向右,无论走多远都只有一种方法,所以当k>=0,f(0,k)=f(k,0)=1;这里值得注意的是f(0,0)=1,(自己想一想为什么),不要问我,因为我也不是很清楚。。。。。这儿就是递归的初始条件。
定义二维数组搜索马周围的控制点,赋值为1.
#include<bits/stdc++.h>
using namespace std;
int d[9][2] = { -2,-1,0,0,1,2,1,-2,-1,2,-1,-2,2,1,2,-1,-2,1 };
int cc[22][22];
long long f[22][22];
int main()
{
int x, y, n, m;
cin >> x >> y >> n >> m;
for (int i = 0; i < 9; i++)
{
int tempx = n + d[i][0], tempy = m + d[i][1];
if (tempx >= 0 && tempx <= x && tempy >= 0 && tempy <= y)
cc[tempx][tempy] = 1;
}
f[0][0] = 1 - cc[0][0];
for(int i=0;i<=x;i++)
for (int j = 0; j <= y; j++)
{
if (cc[i][j] == 1)
continue;
if (i)//如果不在横轴上
f[i][j] += f[i - 1][j];
if (j)//如果不在纵轴上
f[i][j] += f[i][j - 1];
}
cout << f[x][y];
return 0;
}
P1044 [NOIP2003 普及组] 栈 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1044
递推思想:假设i个元素有f(i)种方法,要求n个元素的出管方式,依题意,每个元素都有可能最后一个出去,那我们假设第k个小球最后一个出去有h(k)种方法,那么比k早入管且早出去的有k-1个数,则有f(k-1)种,比k晚出管且早出去的有f(n-k)种。独立性,所以h(k)=f(k-1)*f(n-k)
那么每个元素都有机会最后一次出管,所以f(n)=h(1)+h(2)+ +h(n-1)+h(n)=f(0)*f(n-1)+f(1)*f(n-2)+
+f(n-2)*f(1)+f(0)*f(n-1).
代码实现起来就很简单了。如下。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{//i个元素有f【i】钟出管方式
int n;
cin >> n;
int f[20] = { 1,1,2 };
for (int i = 3; i <= n; i++)
{
for (int j = 0; j < n; j++)
f[i] += f[j] * f[i - j - 1];
}
cout << f[n];
return 0;
}
接下来就是递归和记忆化搜索了
题目描述
我们要求找出具有下列性质数的个数(包含输入的正整数 nn)。
先输入一个正整数 nn(n \le 1000n≤1000),然后对此正整数按照如下方法进行处理:
不作任何处理;
在它的左边加上一个正整数,但该正整数不能超过原数的一半;
加上数后,继续按此规则进行处理,直到不能再加正整数为止。
思路很简单,把一个数一直分下去,加不加左边跟这题没关系,函数递归地结束条件为分到1不能再分了。
我们很容易写下下面的函数:
int ss(int x)
{
int ans = 1;
if (x==-1)
return 1;
for (int i = 1; i <= x / 2; i++)
ans += ss(i);
return ans;
}
当然并没有什么问题,但是只能通过数据小的,运行效率低会时间超限,举个例子,比如说我们递归ss(2),它可能也会被ss(8),ss(4)调用,但是ss(2)的值是一只不变的,在这却被重复计算了很多次。既然它的值是不变的,我们算一个保存一个,下次直接调用,不用再一直递归下去了。
这就是记忆化搜索。
将数组初始化为-1,代表没被搜索保存过。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int d[1005];
int ss(int x)
{
int ans = 1;
if (d[x]!=-1)
return d[x];
for (int i = 1; i <= x / 2; i++)
{
ans += ss(i);
}
return d[x]=ans;
}
int main()
{
int n;
cin >> n;
memset(d, -1, sizeof(d));
d[1] = 1;
int ans = ss(n);
cout << ans;
return 0;
}
P1928 外星密码 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P1928这题很容易想到用函数来实现递归,但是能不能实现就是另一回事了,,,,
我们直接输入一个字符串不好查找后面,那就一个一个输,又是一个小技巧。
举个例子:AC[3FUN]
首先,我们找到了被压缩的字符串:3FUN
对3FUN
进行解压,得到FUNFUNFUN
再把原来的字符串AC
后面添上FUNFUNFUN
即可。
由于这个只有一重「压缩」,可能递归思想体现的不太明显,这里再举一个多重「压缩」的例子:
SAD[4MLE[2TLE[2RE[2WA]]]
首先找到被「压缩」的部分:
[2MLE[2TLE[2RE[2WA]]]]
(从此处开始剩下的部分就是递归的内容了,全部由程序自主实现)
对这个部分进行解压,找到被「压缩」的部分:
[2TLE[2RE[2WA]]]
再对这个部分进行解压,找到被「压缩」的部分:
[2RE[2WA]]
再对这个部分进行解压,找到被「压缩」的部分:
[2WA]
(开始一层一层跳出递归)
对这个部分进行解压并加到前一个字符串的末尾:
[2REWAWA]
再对这个部分进行解压并加到前一个字符串的末尾:
[2TLEREWAWAREWAWA]
再对这个部分进行解压并加到前一个字符串的末尾:
[2MLETLEREWAWAREWAWATLEREWAWAREWAWA]
再对这个部分进行解压并加到前一个字符串的末尾:
SADMLETLEREWAWAREWAWATLEREWAWAREWAWAMLETLEREWAWAREWAWATLEREWAWAREWAWA
至此,递归结束,「密码」破译完毕。
所以,我们只需要找到被「压缩」的子串,并把这个字符串扔给「解压缩」程序即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string xx()
{
string s="",x;
char c;
int d;
while (cin >> c)
{
if (c == '[')//发现一个压缩区
{
cin >> d;
x = xx();
while (d--)
s += x;
}
else if (c == ']')
return s;
else
s += c;
}
return s;
}
int main()
{
cout << xx();
return 0;
}