zoukankan      html  css  js  c++  java
  • 重温——递推递归与搜索

    更新记录

    【1】2020.05.16-11:02

    1.完善内容

    正文

    观前提示

    1. 此文章为重温系列,并不会讲的很细致
    2. 需要有一定递推递归与搜索基础

    递推

    前言

    虽说是递推,但是它与动态规划有着密不可分的关系
    (我动态规划连普及的水平都达不到)
    所以说,递推这东西最简单,但又是初学者最难理解的东西

    为什么说是简单呢?

    • 单独拎出这个算法来,你会发现你什么也讲不了

    为什么说难理解呢?

    • 结合到题里面的时候,有时候你会发现你连思路都捋不出来

    所以这里我们结合题目来讲

    斐波那契数列

    定位:基础题 / 入门难度

    当然我这里指的裸题(你FFT+斐波那契数列出大问题)
    (f[i]=f[i-1]+f[i-2])

    注意一下边界条件
    然后就没了

    没啥可说的,注意到(i-1)(i-2)都被求过,想到用数组来存储就可以了

    #include<iostream>
    using namespace std;
    int a[1000100],num=0,in;
    int main(){
    	a[1]=1;a[2]=1;cin>>num;
    	for(int i=3;i<1000002;i++)
    		a[i]=(a[i-1]+a[i-2])
    	for(int i=0;i<num;i++){
    		cin>>in;cout<<a[in]<<"
    ";
    	}
    }
    

    位数问题

    定位:基础题 / 普及-难度

    这道题做起来依然很简单,只不过过程有点考验大家

    两位数中,带有奇数个3的是:
    (13,23,43,53,63,73,83,93)
    (30,31,32,34,35,36,37,38,39)

    那么我们观察发现:当3不在最高位的时候,数量乘8

    • 高位不能为0或3

    在最高位的时候数量乘9

    • 不是最高位的时候可以为0但不能为3

    依此类推,三位数中含有奇数个3的数的数量为:

    • 个位
      ((103,113,123,143,153,163,173,183,193))
      共9×8=72个
    • 十位
      ((130,131,132,134,135,136,137,138,139))
      共9×8=72个
    • 百位
      ((301,302,304,305,306...398,399))
      共100-9-9=81个
    • 其他
      ((333))
      共1个

    所以含有奇数个3的数的数量为:72×2+81+1=226个

    定义数组(f[i][2])

    • (f[i][0])表示i位数中含有偶数个3的数的数量
    • (f[i][1])表示i位数中含有奇数个3的数的数量

    动态转移方程为:
    (f[i][0]=(f[i-1][0]*9+f[i-1][1])%12345)
    (f[i][1]=(f[i-1][1]*9+f[i-1][0])%12345)

    注意最高位单独乘8就可以了

    #include<iostream>
    using namespace std;
    int f[20001][2],n;
    int main(){
    	cin>>n;f[1][0]=9;f[1][1]=1;
    	for(int i=2;i<n;i++){
    		f[i][0]=(f[i-1][0]*9+f[i-1][1])%12345;
    		f[i][1]=(f[i-1][1]*9+f[i-1][0])%12345;
    	}
    	if(n!=1){
    		f[n][0]=f[n-1][0]*8+f[n-1][1];
    		f[n][1]=f[n-1][1]*8+f[n-1][0];
    	}
    	cout<<f[n][0]%12345;
    }
    
    

    踩方格

    定位:练习题 / 普及难度

    这个题的代码量真的是非常少,但是这个思路的话还是不太好想

    很多人都被这不能再走第二次迷惑了
    但是我们只需要分开方向扫一遍即可

    定义(f[i][3])

    • (f[i][0])表示第i步向上走
    • (f[i][1])表示第i步向左走
    • (f[i][2])表示第i步向右走
    #include<iostream>
    using namespace std;
    int n,f[25][3];
    int main(){
    	cin>>n;f[1][0]=1;f[1][1]=1;f[1][2]=1;
    	for(int i=2;i<=n;i++){
    		f[i][0]=f[i-1][0]+f[i-1][1]+f[i-1][2];
    		f[i][1]=f[i-1][0]+f[i-1][1];
    		f[i][2]=f[i-1][0]+f[i-1][2];
    	}
    	cout<<f[n][0]+f[n][1]+f[n][2];
    //最后加起来就是答案
    }
    

    山区建小学

    定位:思考题 / 提高难度

    这道题考察的比较全面,就其中的动规来说,代码很少但是比较难想出来

    定义(b[i][o])(f[i][o])
    (b[i][o])表示i到o如果建一所小学的话,i到o上所有的村庄到这所小学的距离和
    (f[i][o])表示前i个村庄修o所小学的最优解

    #include<iostream>
    #include<cmath>
    using namespace std;
    int n,m,a[501],mid,i,o,p;
    int b[501][501],f[501][501];
    int main(){
    	cin>>n>>m;
    	for(i=2;i<=n;i++){
    		cin>>a[i];a[i]+=a[i-1];
    	}
    //这里用前缀和用来求两点间距离
    	for(i=1;i<=n;i++){
    		for(o=i;o<=n;o++){
    			mid=(i+o)/2;
    			for(p=i;p<=o;p++)
    				b[i][o]+=abs(a[mid]-a[p]);
    		}
    	}
    //显然,两点之间的中点一定是b[i][o]的最小值
    	for(int i=0;i<=n;i++){
    		for(int o=0;o<=m;o++)
    			f[i][o]=99999999;
    	}
    //初始化
    	f[0][0]=0;
    	for(i=1;i<=n;i++){
    		for(o=1;o<=m;o++){
    			if(o>i){
    				f[i][o]=0;continue;
    			}
    //要修的学校比村庄数还多=>每个村庄家门口就有小学=>距离和为零
    			for(p=o-1;p<i;p++)
    				f[i][o]=min(f[i][o],f[p][o-1]+b[p+1][i]);
    //对前i个村庄枚举
    		}
    	}
    	cout<<f[n][m];
    //输出
    }
    

    递归

    其实递归可以理解为动规的另一种写法(只不过更慢,占内存更多就是了)

    数的计数

    定位:基础题 / 普及-难度

    这道题就是一道(反递归)送经验题

    递归思路:左边从1开始添加到n/2
    添加完毕后进行下一步的递归

    递归版本超时1毫秒

    #include<cstdio>
    int n,num=1;
    inline void pan(int n){
    	for(int i=1;i<=(n>>1);++i){
    		num+=1;pan(i);
    	}
    }
    int main(){
    	scanf("%d",&n);
    	pan(n);
    	printf("%d",num);
    }
    

    超时可不太好,所以我们换成动规
    从1扫到n就可以了

    小小的证明:

    • n左边只能添加比起小一半的数
    • 但是这个添加的数(因为比n小)我们之前计算过了
    • 直接加上就好
    • 注意边界条件

    换成动规就完美AC了

    #include<iostream>
    using namespace std;
    long long int f[1002];int a;
    int main()
    {
    	f[1]=1;
    	for(int i=2;i<=1001;i++){
    		for(int o=1;o<=i/2;o++)
    			f[i]+=f[o];
    		f[i]+=1;
    	}
    	cin>>a;cout<<f[a];
    }
    

    所以说还是能用动规就用动规,递推TLE和MLE让你玩不起

    搜索

    (这个算法最大的贡献就是证明了递归并不是一无是处)

    通俗来讲就是我们在一个决策点上会遇到许多不同的情况
    选择一种之后又会有许多不同的情况
    但存在边界并且决策点有限

    于是暴力递归————搜索算法就出现了!!

    搜索,就是把所有情况都遍历一遍
    不过时间与内存什么的都会很高

    有时候还会用到回溯,剪枝(区间动规)等一系列玄学操作

    马走日

    定位:基础题 / 普及难度

    没啥说的,每到一步扩展就行了

    #include<iostream>
    using namespace std;
    int t,n,m,x,y,num;
    bool bl[101][101];
    int sx[8]={2,1,-1,-2,2,1,-1,-2};
    int sy[8]={1,2,2,1,-1,-2,-2,-1};
    void pan(int x,int y,int s){
    	if(x<0||y<0||x>=n||y>=m) return;
    	if(s==n*m) num+=1;
    	bl[x][y]=1;
    //进行下一步时暂时标记这个点用过
    	for(int i=0;i<8;i++)
    		if(!bl[x+sx[i]][y+sy[i]])
    			pan(x+sx[i],y+sy[i],s+1);
    //核心,向8个方向扩展
    	bl[x][y]=0;
    //这一步搜索完了,标记没用过
    }
    int main(){
    	cin>>t;
    	for(int i=0;i<t;i++){
    		num=0;cin>>n>>m>>x>>y;
    		pan(x,y,1);cout<<num<<"
    ";
    	}
    }
    

    单词接龙

    定位:练习题 / 普及难度

    整理题目可得:

    • 一个单词最多用两次

    • 单词可以不全用完

    • 不可以包含

    • 重叠部分应该越少越好

    所以说实现一个判断重叠部分长度的函数
    搜一遍即可

    #include<iostream>
    using namespace std;
    string a[50];
    int n,cnt[50],num,maxn;
    char begin;
    int yn(int a,int b){
    	int ayn=1,al=(::a[a].length());
    	for(int i=al-1;i>=0;i--){
    		ayn=1;
    		if((::a[a][i])==(::a[b][0])){
    			for(int o=i;o<al;o++){
    				if((::a[a][o])!=(::a[b][o-i])){
    					ayn=0;break;
    				}
    			}
    			if(ayn)
    				return al-i;
    		}
    	}
    	return 0;
    }
    void fs(int wei,int l,int p){
    	if(l>maxn)
    		maxn=l;
    	for(int i=0;i<n;i++){
    		if(cnt[i]>=2) continue;
    		int c=yn(wei,i);
    		if(c&&(c!=a[wei].length()||c!=a[i].length())){
    			cnt[i]+=1;fs(i,l+a[i].length()-c,p+1);cnt[i]-=1;
    		}
    		
    	}
    }
    int main(){
    	cin>>n;
    	for(int i=0;i<n;i++)
    		cin>>a[i];
    	cin>>begin;
    	for(int i=0;i<n;i++){
    		if(a[i][0]==begin){
    			cnt[i]+=1;fs(i,a[i].length(),1);cnt[i]-=1;
    		}
    	}
    	cout<<maxn;
    }
    
    

    总结

    • 这三个算法相互联系,学习时注意均衡
  • 相关阅读:
    基于maven构建javaweb项目思路梳理及改进 在路上
    分圆多项式整理
    第03次作业栈和队列
    C语言第二次实验报告
    week01绪论作业
    第02次作业线性表
    C语言第一次实验报告
    工作流的问题
    无法使用Outlook 2003 Out Of Office Assisant
    刷机
  • 原文地址:https://www.cnblogs.com/zythonc/p/12899497.html
Copyright © 2011-2022 走看看