2025-2-12:第二题正确代码有误已修改:)
2025-5-8:第五题附注与洛谷题目样例不同,避免误用:)
目录
———————————————————————————————————————————
0.回归
今天本蒟蒻也是脑袋一热灵光一闪准备退坑更新一篇新的水文章大作——递归的记忆化搜索
什么?问我为什么不先更新递归算法?当然我懒发现这递归有点多,所以暂时放弃没法更新完,反正开学前肯定会出的哈
1.What is 记忆化搜索?(又变沙雕了)
回归正题,记忆化搜索又叫递归剪枝,当然,肯定是用来省时间的。
记忆化搜索简单来说开个数组存一下那个啥来着.....哦对那个记录过的内容。咳咳,比如说啊,你计算了1+1=2,然后你就把这个内容给扔到一个数组(例如f)里面,后面一旦要用直接return f里面的结果就行了,像这种递归可能要用几十万次甚至几百万次的1+1=2来说,确实比较省时间。
好的,那我们可以通过例题来深度了解一下如何去用这个记忆化搜索。
2.例题~~~~~
咳咳,先来道简单点的。其实都不难
这道题可是c++入门的钉子户,想躲?不可能!
1.斐波那契数列
看到这个题目就知道,又来了啊啊啊啊啊啊
当然,先看题目
①题干
②解题思路
这熟悉的题干, 就是这n值嘛,有点大。
递推肯定能解决(要看看递推那章去,这里不写)
我们可以先用正常递归试一试。
#include <bits/stdc++.h>
using namespace std;
long long n;
long long dfs(int n){
if(n <= 2) return 1;
return dfs(n-1) + dfs(n-2);
}
int main(){
cin >> n;
cout << dfs(n) << endl;
return 0;
}
然后看一下结果:
What?!
再看下错误原因:
咳咳,这就不好了。
超时了!!!!
仔细一想,你45起码也得遍历几十亿次了,不超时?想得美!
那咋办?那肯定得写递推让记忆化搜索上场了!
首先,一个数组肯定要有:
long long f[55];//这里开到55是防止越界RE
接下来要去记录,记录是可以直接写在return里的:
return f[n] = dfs(n-1) + dfs(n-2);
怎么用呢?那还不简单,直接提前判断不就行了:
if(f[n]) return f[n];
再把代码一加:
#include <bits/stdc++.h>
using namespace std;
long long n;
long long f[55];
long long dfs(int n){
if(f[n]) return f[n];
if(n <= 2) return 1;
return f[n] = dfs(n-1) + dfs(n-2);
}
int main(){
cin >> n;
cout << dfs(n) << endl;
return 0;
}
太简单辣!咳咳,但之后肯定不简单哈(骗你的,都简单)
下一题
2.超级简单的递推函数
emm,简单么?
①题干
②.沙雕. 解题思路
小明:这啥啊,这么简单,用你刚刚的方法编一下就行了。 看我的:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e9 + 7;
int n,k;
long long f[1005][1005];
long long dfs(int n,int k){
if(f[n][k]) return f[n][k];
if(k == n) return 1;
if(k == 0 || k > n) return 0;
if(k >= 1 && k < n) return f[n][k] = dfs(n-1,k-1) + 1ll * k * dfs(n-1,k);
}
int main(){
cin >> n >> k;
cout << dfs(n,k) % N << endl;
return 0;
}
小明:切,这就编完了,你试试呐,嘿嘿,我连1ll都想到了,看你怎么说。
嘿,你还别说,看下结果。
废了。
当然,原因也很明显,你这在dfs里都超long long范围了,你最后去模N还有什么用啊?!
所以,我们应该在它return 的时候就模N:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e9 + 7;
int n,k;
long long f[1005][1005];
long long dfs(int n,int k){
if(f[n][k]) return f[n][k];
if(k == n) return 1;
if(k == 0 || k > n) return 0;
if(k >= 1 && k < n) return f[n][k] = (dfs(n-1,k-1) + 1ll * k * dfs(n-1,k)) % N;
}
int main(){
cin >> n >> k;
cout << dfs(n,k) % N << endl;//这里再模N,因为最后给出的结果不一定 < 1e9+7
return 0;
}
完美好,下一题。
小明:补食啊喂!!!!
3.3n+1猜想(2024信息素养大赛国赛有出过类似题)
说实话,当时5道题,我前3题非常轻松做完了,这是第4题,我当时老是超时,后来学了记忆化才知道怎样AC(说多了都是泪啊o(╥﹏╥)o)
今天重新温故一下:
①题干
②解题思路
这题目一看正常递归就会超,所以呢,那必须记忆化啊(其他方法不讲,这是专题)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long //为懒人(我)加的,typedef和using都可以哈
ll q,f[10000005];
ll dfs(ll x){
if(f[x]) return f[x];
if(x == 1) return 1;
if(x % 2 == 0) return f[x] = dfs(x / 2) + 1;
else return f[x] = dfs(3 * x + 1) + 1;
}
int main(){
scanf("%lld",&q); //这里数据比较大,用scanf和printf快一点
while(q--){ //这种用for也可以,但毕竟不用循环变量,懒点用while也行
ll x;
scanf("%lld",&x);
printf("%lld\n",dfs(x));
}
return 0;
}
但是一看,啥?!50?!RE?!
补食这...
记忆化也用了,为啥RE?
[一本正经] 这里嘛,你会发现一旦这个验算时的数据一旦大于x了,也就是数组,它放不下了!
小明:那咋整,开个几十亿的数组?
孩子,你确定不会出问题?自己去试吧
那数组也开不大了,只能特判了。
至于1e7以下的就还是省省时间吧。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
ll q,f[10000005];
ll dfs(ll x){
if(x > 1e7){
if(x % 2 == 0) return dfs(x / 2) + 1;
else return dfs(3 * x + 1) + 1;
}
if(f[x]) return f[x];
if(x == 1) return 1;
if(x % 2 == 0) return f[x] = dfs(x / 2) + 1;
else return f[x] = dfs(3 * x + 1) + 1;
}
int main(){
scanf("%lld",&q);
while(q--){
ll x;
scanf("%lld",&x);
printf("%lld\n",dfs(x));
}
return 0;
}
好的,不能再快了(至少dfs里),下一题吧。
4.变换:从1到B
这题看题目还是有点懵的,看题干:
①题干
②解题思路
emm这B有点大,所以记忆化
所以直接来:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int b,d,p;
long long f[1000005];
long long dfs(int x){
if(x > b) return 1e9; //大于B没必要再继续,返回大值结束自调用
if(f[x]) return f[x];
if(x == b) return 0; //等于B后不需要变,return 0
return f[x] = min(dfs(p * x + 1),dfs(x + d)) + 1; //注意要加1,不然没有次数增加了
//由于是最小,所以取p*x+1和x+d中步数少的(别告诉我你不知道min)
}
int main(){
cin >> b >> d >> p;
long long ans = dfs(1); //从1到B先从1开始
if(ans >= 1e9) cout << -1; //一旦变不了也就是返回1e9,就输出-1
else cout << ans;
return 0;
}
可以下一题了,等等,那个1e9千万别开1e6以下,自己去看。
前方高能!!!!高啥能,就难一点
5.滑雪(和洛谷P1434的样例不大一样,但这代码改个地方就能用,自己找,我就用这个代码过的awa)
这题直接看:
这题一看就重量级(不会看不清吧),这题光dfs还是太多了,所以记忆化!!!
咋用。
额(⊙o⊙)…不知道。
小明:!&*#¥!*¥¥&!*¥#!&@¥!*¥……!@*&¥%!&*¥%……*!@&¥……
开个玩笑你骂啥。
来吧,首先这题目中的滑雪坡有很多条,所以你一个一个遍历肯定会超,但是,其实这道题有个方式来记忆化——
你会发现,你(1,1)的遍历完后是不是知道了在(1,1)位置时的方案总数,那当你遍历(1,2)时往(1,1)滑时是不是可以直接用(1,1)的结果呢?
漂亮!
那么就可以随手编出如下函数:
int dfs(int x,int y){
if(f[x][y]) return f[x][y];
int len = 1;
for(int i = 0;i <= 3;i++){
int nx = x + dx[i];
int ny = y + dy[i];
//由于可向4个方向遍历,开位移数组更方便
if(nx >= 1 && ny >= 1 && nx <= r && ny <= c && a[nx][ny] <= a[x][y]){
//前4个条件判断是否越界,最后一个判断是否是下坡
len = max(dfs(nx,ny)+1,len);
//这里比较新雪坡和原长度最长的雪坡长度,如果新雪坡长,就替换;反之就不变(记得+1)
}
}
return f[x][y] = len;
//记忆化并返回len
}
再加上一些主函数:
#include <bits/stdc++.h>
using namespace std;
int r,c,ans;
int f[105][105],a[105][105];
//这是上下左右4方向的位移数组,背熟!!!
int dx[] = {-1,1,0,0};
int dy[] = {0,0,-1,1};
int dfs(int x,int y){
if(f[x][y]) return f[x][y];
int len = 1;
for(int i = 0;i <= 3;i++){
int nx = x + dx[i];
int ny = y + dy[i];
if(nx >= 1 && ny >= 1 && nx <= r && ny <= c && a[nx][ny] <= a[x][y]){
len = max(dfs(nx,ny)+1,len);
}
}
return f[x][y] = len;
}
int main(){
cin >> r >> c;
for(int i = 1;i <= r;i++)
for(int j = 1;j <= c;j++)
cin >> a[i][j];
for(int i = 1;i <= r;i++)
for(int j = 1;j <= c;j++)
//这里比较每个地方滑下来的长度,取最大的
ans = max(ans,dfs(i,j));
cout << ans;
return 0;
}
So easy yet?
小明:NO!GOD!PLEASE!NO!NO!!!!
e......
算了,不管他发癫,结束,886,记得三连哈!