C C++最全acwing-蓝桥杯C++ AB组辅导课Day2-递归习题+递推+二分,2024年最新深入剖析原理

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

#include<bits/stdc++.h>

using namespace std;

int main(){
    int n;
    cin>>n;
    int f[46]={0};
    f[1] = 0;
    f[2] = 1;
    for(int i = 3;i<=n;i++){
        f[i] = f[i-1] + f[i-2];
    }
    for(int i=1;i<=n;i++){
        cout<<f[i]<<" ";
    }
    return 0;
}

采取滚动数组的思想:

#include<bits/stdc++.h>

using namespace std;

int main(){
    int n;
    cin>>n;
    int a = 0,b = 1;
    for(int i = 1;i<=n;i++){
        cout<<a<<" ";
        int f = a+b;
        a = b;
        b = f;
    }

    return 0;
}
2.费解的开关

题目链接:95. 费解的开关 - AcWing题库

题意:

改变一个灯的状态会将他上下左右的灯的状态都改变。需要使用最少次数修改灯的状态使得所有的灯变成亮着的。

解题思路:

找到题目隐藏信息,1.所有开关只能按一次,按两次相当于没按。2.灯的状态跟按开关的顺序无关,无论以什么顺序按开关,灯的状态相同。

我们发现,假如可以枚举第一行的操作(是否按开关),枚举完后,我们不能对第一行再进行操作(因为所有开关只能按一次,并且我们枚举了第一行的按开关的所有操作),**此时我们需要按第二行的开关,并且操作被第一行灯的亮灭状态所唯一决定。(递推)**当第二行操作结束后,第三行的操作也被唯一决定。以此类推,直到n-1行操作完成,此时前n-1行的状态全部为亮,只需查看最后一行是否为量即可判断该方案是否可取,如果可取,更新最小答案。

代码:
代码实现有不少细节的地方,比如turn函数中传入坐标x,y不能直接使用,应该用a,b代替。因为坐标会在for循环中被修改,而实际上传进来的x,y坐标不应该变。

还有就是memcpy函数对原数组进行备份, 因为for循环枚举所有方案,每次方案开始时要保证都是原来的数组,所以要备份。在每个方案结束后再将数组还原。

#include<bits/stdc++.h>
using namespace std;
char light[6][6],backup[6][6];

int dx[5]={0,1,0,-1,0},dy[5]={1,0,-1,0,0};
void turn(int x,int y){
    
    for(int i=0;i<5;i++){
        int a = x + dx[i];
        int b = y + dy[i];
        if(a<0||a>=5||b<0||b>=5) continue;
        if(light[a][b] == '0') light[a][b] = '1';
        else light[a][b] = '0';
    }
    return;
}

int main(){
    int n;
    cin>>n;
    for(int a=0;a<n;a++){
        for(int i=0;i<5;i++) cin>>light[i];
        int res = 10;
        for(int op=0;op<32;op++){
            memcpy(backup,light,sizeof light);
            int step = 0;
            for(int k=0;k<5;k++){
                if(op>>k&1){
                    turn(0,k);
                    step++;
                    // cout<<1111<<endl;
                }
            }
            for(int i=0;i<4;i++){
                for(int j=0;j<5;j++){
                    if(light[i][j] == '0'){
                        turn(i+1,j);
                        step++;
                    }
                }
            }
            bool dark = false;
            for(int i=0;i<5;i++){
                if(light[4][i] == '0') dark = true;
            }
            if(!dark) res = min(res,step);
            memcpy(light,backup,sizeof backup);
        }
        if(res>6) res = -1;
        cout<<res<<endl;
    }
    return 0;
}

递归习题:

1.翻硬币

解题思路:

挖掘题目信息1.所有硬币的中间相当于有个开关,按一次会将开关两侧的硬币翻转。2.按开关的顺序与硬币状态无关。3.开关只能按一次,按两次相当于没按。

考虑按顺序按开关,发现假如前面的开关已经按下的话,后面开关是否需要被按被前面硬币的状态所唯一决定。(递推)所以按顺序按开关就完事了。

代码:

#include<bits/stdc++.h>

using namespace std;

string a,b;
void turn(int i){
    if(a[i]=='*'){
        a[i] = 'o';
        if(a[i+1] == '*'){
            a[i+1] = 'o';
        }else{
            a[i+1] = '*';
        }
    }else{
        a[i] = '*';
        if(a[i+1] == '*'){
            a[i+1] = 'o';
        }else{
            a[i+1] = '*';
        }
    }
    return;
}


int main(){
    cin>>a;
    cin>>b;
    int n = a.size();
    int step = 0;
    for(int i=0;i<n-1;i++){
        if(a[i]!=b[i]){
            turn(i);
            step++;
        }
    }
    cout<<step;
    return 0;
}
2.飞行员兄弟

题目链接:116. 飞行员兄弟 - AcWing题库

题意:
费解的开关的简化版,需要令一个4x4的矩阵全部变成’-‘。矩阵的字符可能为’+‘或’-'。当按动一个开关,同列的状态和同行的状态都会被改变。题目要求输出最小步骤数和操作的开关位置。

解题思路:
由于题目范围不大,只有总共16个数,可以考虑指数型枚举所有开关的操作。复杂度为2^16。对于每个操作,判断结果是否符合预期,如果符合,那么更新最小步骤数。

代码:

代码实现方面有个小点没想到,在turn函数中for循环中会令[x,y]处状态修改两次,所以后面要单独再修改一次。

#include<bits/stdc++.h>

using namespace std;
char fridge[5][5],backup[5][5];

void turn(int x,int y){
    for(int i=0;i<4;i++){
        if(fridge[x][i] == '+'){
            fridge[x][i] = '-';
        }else fridge[x][i] = '+';
        
        if(fridge[i][y] == '+'){
            fridge[i][y] = '-';
        }else fridge[i][y] = '+';
    }
    //经过前面的处理,[x,y]的位置会被修改两次,这里要修改回来。
    if(fridge[x][y] == '+'){
            fridge[x][y] = '-';
        }else fridge[x][y] = '+';
    return;
}


int main(){
    for(int i=0;i<4;i++) cin>>fridge[i];
    int res = 100;
    vector<pair<int,int>> ans;
    for(int op=0;op<65536;op++){
        memcpy(backup,fridge,sizeof fridge);
        int step = 0;
        vector<pair<int,int>> sp;
        for(int i=0;i<16;i++){  //枚举每一位
            if(op>>i&1){
                int x = i/4;
                int y = i%4;
                turn(x,y);
                // cout<<x<<y<<endl;
                sp.push_back({x,y});
                step++;
            }
        }
        //判断方案是否可行
        bool yes = true;
        for(int i=0;i<4;i++){
            for(int j=0;j<4;j++){
                if(fridge[i][j] == '+'){
                    yes = false;
                    // break;
                }
            }
            // if(!yes) break;
        }
        if(yes&&step<res){
            cout<<step<<endl;
            res = step;
            ans = sp;
        }
        memcpy(fridge,backup,sizeof backup);
    }
    // cout<<ans.size()<<endl;
    for(int i=0;i<ans.size();i++){
        cout<<ans[i].first+1<<' '<<ans[i].second+1<<endl;
    }
    return 0;
}

整数二分问题的思路:

模板使用的时候只需要判断是L=M还是R=M,据此判断使用模板1还是模板2。

二分例题:

1.数的范围

题目链接:789. 数的范围 - AcWing题库

题意:给n个非递减的数,找出某个数值的起始位置和结束位置。

二分模板的选择:

右区间的左端点时选第一个模板,选左区间的右端点时选第二个模板。

bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

解题思路:
首先考虑题目是否可以二分,答案一定在题目给定的区间范围内,并且可以使用二段性(是否大于等于/小于等于)分隔区间,并且答案是区间的端点,所以可以使用二分。

代码:
二分起始位置时,因为判断的是是否大于等于目标值,所以答案是右区间的左端点,所以使用模板1,二分结束位置时,因为判断的是是否小于等于目标值,所以答案是左区间的右端点,所以使用模板2。

#include<bits/stdc++.h>

using namespace std;
int num[100010];
int main(){
    int n,q;
    cin>>n>>q;
    for(int i=0;i<n;i++) scanf("%d",&num[i]);
    while(q--){
        int k ;
        cin>>k;
        int l = 0,r = n-1;
        while(l<r){
            int mid = l+r>>1;
            if(num[mid]>=k) r = mid;
            else l = mid+1;
        }
        if(num[l] == k){
            cout<<l<<' ';
            l = 0;
            r = n-1;
            while(l<r){
                int mid = l+r+1>>1;
                if(num[mid]<=k) l = mid;
                else r = mid-1;
            }
            cout<<l<<endl;
        }else{
            cout<<"-1 -1"<<endl;
        }
    }
    return 0;
}
2.数的三次方根

题目链接:790. 数的三次方根 - AcWing题库

题意:给一个数,找它的三次方根。

解题思路:

直接二分区间,判断mid的三次方是否小于真值,更新区间即可。

代码:

#include<bits/stdc++.h>

using namespace std;

int main(){
    double n;
    cin>>n;
    double l = -10000,r = 10000;
    while(r-l>1e-8){
        double m = (l+r)/2;
        if(m*m*m>n){
            r = m;            
        }else l = m;
    }
    printf("%lf",l);
    return 0;
}

二分习题:

1.机器人跳跃问题

题目链接:730. 机器人跳跃问题 - AcWing题库

题意:机器人根据开始的能量值e进行跳跃,跳到比当前更高的楼会减少h[i]-e能量值,跳到比当前更低的楼会增加e-h[i]能量值,期间能量值若为负数,则失败。题目要求返回能够跳到最后一栋楼最小的能量值。

解题思路:
首先题目问能够满足条件的最小数,十分满足二分的解题思路。答案一定在题目给定的区间内,可以找到二段性,区间左侧不满足题意,区间右侧满足题意,并且我们要找的答案就是右侧区间的左端点(即最小值)。

代码:
题目细节处在于,当e>100000时就可以确定一定能跳过所有楼层了。但如果不加判断,e有可能会不断增加,最后爆int导致错误。

#include<bits/stdc++.h>
using namespace std;

int h[100010];
int n;

bool check(int e){

    for(int i=1;i<=n;i++){
        if(h[i] > e){
            // cout<<"e:"<<e<<" h:"<<h[i]<<endl;
            e -= (h[i]-e);
        }else{
            e += (e-h[i]);      //由于差值越大,加的数越大,所以这里可能会爆int变成负数
        }
        if(e<0) return false;
        if(e>100000) return true;
    }
    return true;
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&h[i]);
    } 
    // for(int i=1;i<=n;i++) cout<<h[i]<<" ";


![img](https://img-blog.csdnimg.cn/img_convert/843a5a009d1094ecfd0027c1846194d0.png)
![img](https://img-blog.csdnimg.cn/img_convert/5c4ba8f058e0f180b85e26b5ca9d95bc.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

return true;
    }
    return true;
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        scanf("%d",&h[i]);
    } 
    // for(int i=1;i<=n;i++) cout<<h[i]<<" ";


[外链图片转存中...(img-6caAlBvn-1715681008696)]
[外链图片转存中...(img-CPOSvrLL-1715681008696)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 24
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值