递归、分治算法刷题笔记

递归

  1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型
  2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出
  3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程
  4. 将每一步封装好,抽象成一个行动或者处理,然后递归调用,并设定终止条件(汉诺塔 —— 将每一步设定为将当前所有小盘放在2号柱上,将最大的盘放在3号柱上,再将所有小盘放到1号柱上。 终止条件 —— 只有1个盘时,直接放在3号柱上

汉诺塔

在这里插入图片描述
该游戏有3个柱子和一组不同大小的圆盘,柱子从圆盘的中心穿过。游戏开始时,所有圆盘叠放在左侧第一个柱子上,游戏的目标是将所有的圆盘从第一个柱子移动到第三个柱子,同时遵守以下规则:

  1. 除了被移动时,所有圆盘都必须放在柱子上
  2. 一次只能移动一个圆盘
  3. 圆盘不能放置在比它小的圆盘上面
#include <bits/stdc++.h>
using namespace std;
int step;
void move(int n,char A,char B,char C){
    if(n==1){
        step++;
        cout<<"["<<step<<"]move 1# from "<<A<<" to "<<C<<endl;
    }
    else{
        move(n-1,A,C,B);
        cout<<"["<<step<<"]move "<<n<<"# from "<<A<<" to "<<B<<endl;
        step++;
        move(n-1,B,A,C);
    } 
}
int main(){
    int n;
    cout<<"请输入block数n:"<<endl;
    cin>>n;
    move(n,'A','B','C');
    return 0;
}

[NOIP2001]求先序排列

题目描述

给出一棵二叉树的中序与后序排列。求出它的先序排列。(约定树结点用不同的大写字母表示,长度 ≤ 8)。

输入描述

1行,表示一棵二叉树的先序

示例一
输入

BADC
BDCA

输出

ABCD

思路

  • 先搞清楚 先序 (根左右)、中序(左根右) 、后序(左右根)
  • 由题意,显而易见——第2行的输入的最后一个字母就是根
  • 然后根据 找到第1行输入的 的位置,就可以分出左子树和右子树,然后递归
  • 对于后序排列中的右子树,根据 的位置找出来,然后递归
  • 简而言之——将传入函数的树分成左子树和右子树,进行递归即可

题解

#include<bits/stdc++.h>
using namespace std;
void priority(string s1,string s2){
    if(s1=="\0"||s2=="\0")return;
    char root=s1.find(s2[s2.length()-1]);
    cout<<s1[root];
    priority(s1.substr(0,root), s2.substr(0,root));
    priority(s1.substr(root+1), s2.substr(root,s2.size()-1-root));
}

int main(){
    string s1,s2;
    cin>>s1>>s2;
    priority(s1,s2);
}

分治

对于一个规模为n的问题,若该问题可以容易的解决(比如规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解决这些子问题,然后将各个子问题的解合并得到原问题的解
如果原问题可以分割成k个子问题, 1 < k < = n 1<k<=n 1<k<=n,且这些子问题均可解并且利用这些子问题的解求出原问题的解,那么分治方法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归经常同时应用在算法设计之中

使用

  • 该问题的规模缩小到一定的程度就可以容易的解决
  • 该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质
  • 利用该问题分解出的子问题的解可以合并为该问题的解
  • 该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题

第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条,而不具备第三条特征,则可以考虑使用贪心法或者动态规划法
第四条关系到分治法的效率,如果各个子问题是不独立的则分治法要做寻多不必要的工作,重复的解决公共的子问题,此时虽然可用分治法,但一般使用动态规划法较好

步骤

  • 分解:将原问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题
  • 解决:若子问题规模较小而容易被解决则直接解,否则递归地解各个子问题

[NOIP2004]FBI树

题目描述

我们可以把由“0”和“1”组成的字符串分为三类:全“0”串称为B串,全“1”串称为I串,既含“0”又含“1”的串则称为F串。
FBI树是一种二叉树[1],它的结点类型也包括F结点,B结点和I结点三种。由一个长度为2N的“01”串S可以构造出一棵FBI树T,递归的构造方法如下:
(1) T的根结点为R,其类型与串S的类型相同;
(2) 若串S的长度大于1,将串S从中间分开,分为等长的左右子串S1和S2;由左子串S1构造R的左子树T1,由右子串S2构造R的右子树T2。
现在给定一个长度为2N的“01”串,请用上述构造方法构造出一棵FBI树,并输出它的后序遍历[2]序列。

[1] 二叉树:二叉树是结点的有限集合,这个集合或为空集,或由一个根结点和两棵不相交的二叉树组成。这两棵不相交的二叉树分别称为这个根结点的左子树和右子树。 [2] 后序遍历:后序遍历是深度优先遍历二叉树的一种方法,它的递归定义是:先后序遍历左子树,再后序遍历右子树,最后访问根。

输入描述

第一行是一个整数N(0 <= N <= 10)
第二行是一个长度为2N的“01”串。

输出描述

一个字符串,即FBI树的后序遍历序列。

示例一
输入

3
10001011

输出

IBFBBBFIBFIIIFF

备注

对于40%的数据,N <= 2;
对于全部的数据,N<= 10。

思路

  • 分治——将待处理的字符串分为前后两段
  • 递归——分成前后两段使用递归算法继续分,直到到达题目所设定的要求(s.length()>1)
  • 这道题不用太在意先序后序这些,读懂题目就会发现没涉及到
#include<bits/stdc++.h>
using namespace std;
char FBI(string s){
    if(s.length()>1){
        cout<<FBI(s.substr(0,s.length()/2));
        cout<<FBI(s.substr(s.length()/2,s.length()));
    }
    if(s==string(s.length(),'0')) return 'B';
    if(s==string(s.length(),'1')) return 'I';
    else return 'F';
}
int main(){
    int n;
    string s;
    cin>>n>>s;
    cout<<FBI(s);
}

表达式计算4

题目描述

给出一个表达式,其中运算符仅包含+,-,*,/,^(加 减 乘 整除 乘方)要求求出表达式的最终值
数据可能会出现括号情况,还有可能出现多余括号情况
数据保证不会出现≥ 231的答案
数据可能会出现负数情况

输入描述

仅一行,即为表达式

输出描述

仅一行,既为表达式算出的结果

示例一
输入

(2+2)^(1+1)

输出

16

备注

表达式总长度≤30

思路

  • 首先,由题意易知,需要进行递归和分治,将操作符两边的数进行分治、递归
  • (雨巨告诉我们,表达式计算用递归最不容易出错)并且应该以栈的思想去解决表达式计算,以先进后出的顺序来解决。同于我们平常计算先算乘方、乘除、加减,应该是先见到乘方就对乘方进行计算(在计算过程中再递归)。
  • 剩下的具体写在代码注释里

题解

#include<bits/stdc++.h>
using namespace std;
string s;
int num(int l,int r){                     //没什么好说的,就是将字符串变成数字
    int ans=0;
    for(int i=l; i<=r; i++){
        ans*=10;
        ans+=s[i]-'0';
    }
    return ans;
}
int solve(int l,int r){
    int pos1=-1,pos2=-1,pos3=-1;
    int cnt=0;
    if(l>r)return 0;
    if(l==r)return s[l]-'0';
    for(int i=l; i<=r; i++){                              //对字符串相应的区间遍历
        if(s[i]=='(')cnt++;
        if(s[i]==')')cnt--;
        if(cnt==0){
            if(s[i]=='+'||s[i]=='-')pos1=i;         
            if(s[i]=='*'||s[i]=='/')pos2=i;
            if(s[i]=='^')pos3=i;
        }
    }
    if(pos1==-1&&pos2==-1&&pos3==-1){         //算式在括号里或者是只有数字
        if(cnt>0&&s[l]=='(')return solve(l+1,r);
        else if(cnt<0&&s[r]==')')return solve(l,r-1);
        else if(cnt==0&&s[l]=='('&&s[r]==')')return solve(l+1,r-1);
        else return num(l,r);
    }
    if(pos1>=0){                      //这里的判断顺序是加减、乘除、乘方——对应到计算顺序实际乘方、乘除、加减。因为上一个判断已经解决了括号的情况,所以在此的算式都是无需考虑括号的。
        if(s[pos1]=='+')return solve(l,pos1-1)+solve(pos1+1,r);
        else return solve(l,pos1-1)-solve(pos1+1,r);
    }
    else if(pos2>=0)
        if(s[pos2]=='*')return solve(l,pos2-1)*solve(pos2+1,r);
        else return solve(l,pos2-1)/solve(pos2+1,r);
    else if(pos3>=0)
        return pow(solve(l,pos3-1),solve(pos3+1,r));
    else return 0;
}
int main(){
    cin>>s;
    cout<<solve(0, s.length()-1);
}

华华教月月做数学

题目描述

找到了心仪的小姐姐月月后,华华很高兴的和她聊着天。然而月月的作业很多,不能继续陪华华聊天了。华华为了尽快和月月继续聊天,就提出帮她做一部分作业。
月月的其中一项作业是:给定正整数A、B、P,求AB\mod P的值。华华觉得这实在是毫无意义,所以决定写一个程序来做。但是华华并不会写程序,所以这个任务就交给你了。
因为月月的作业很多,所以有T组询问。

输入描述

第一行一个正整数T表示测试数据组数。
接下来T行,每行三个正整数A、B、P,含义如上文。

输出描述

输出T行,每行一个非负整数表示答案。

示例
输入

2
2 5 10
57284938291657 827493857294857 384729583748273

输出

2
18924650048745

备注

1≤T≤103,1≤A,B,P≤1018

思路

  • 看完题目,很容易发现,题目逻辑很简单,但是考察的地方在于时间复杂度上和数据类型的最大容量
  • 像这道题,易知,是肯定不可以直接用pow和普通乘法的
  • 考虑快速幂、加法模拟乘法
  • 快速幂
  • 加法模拟乘法:求7* 5——
    n=5的二进制是101,
    此时n为奇数,则ans=ans+tmp;tmp=tmp+tmp;n右移一位;(ans=0+7,tmp=7* 2)
    此时n为偶数,则直接tmp=tmp+tmp;n右移一位;(ans=7,tmp=7* 4)
    此时n为奇数,则ans=ans+tmp;tmp=tmp+tmp;n右移一位;(ans=7+7* 4,tmp=7*8)

题解

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll mul(ll a,ll b, ll mod){          //快速乘法,结构和快速幂相似,就是ans和tmp由乘法变成加法
    ll ans=0;
    while(b){
        if(b&1) ans=(ans+a)%mod;    //if(b&1)用来判断是否为奇数
        a=(a+a)%mod;
        b>>=1;
    }
    return ans;
}
ll pow(ll a,ll b,ll mod){
    ll ans=1;
    while(b){
        if(b&1) ans=mul(ans,a,mod);
        a=mul(a,a,mod);
        b>>=1;
    }
    return ans;
}
int main(){
    int t;
    ll a,b,mod;
    scanf("%d",&t);
    while(t--){
        cin>>a>>b>>mod;
        cout<<pow(a,b,mod)<<endl;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值