acwing-蓝桥杯C++ AB组辅导课Day2-递归习题+递推+二分(1)

            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.翻硬币


![](https://img-blog.csdnimg.cn/direct/4a208b3258a249fd8e4a713888610aab.png)


解题思路:


挖掘题目信息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。![](https://img-blog.csdnimg.cn/direct/2d2d01bf715a4707afc2a9742601c192.png)


![](https://img-blog.csdnimg.cn/direct/7ae979651d524102854f81ae0f88a622.png)


## 二分例题:


#### 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(mmm>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]<<" ";
//二分答案
int l = 1,r = 100000;
while(l<r){
int mid = l+r>>1;
if(check(mid)) r = mid;
else l = mid+1;
// cout<<l<<’ '<<r<<endl;
}
cout<<l<<endl;
return 0;
}



#### 2.四平方和


题目链接:[1221. 四平方和 - AcWing题库]( )


题意:所有数字都可以写成四个数的平方和,给一个数n,要求返回平方和等于n的字典序最小的四个数。


解题思路:  
 一开始想的是使用二分+哈希,先初始化出来小于根号n的数的平方到数组。每次二分找到小于n的数的平方(左区间的右端点),并让n减去这个数的平方,循环4次,最后判断平方和是否等于n。如果不相等,更新二分区间。这种解法虽然可以找到四个数的平方和=n,但是不是字典序最小,所以不可行。


由于题目要求字典序最小,所以可以考虑从小枚举,每个位置可以枚举的数是根号下5e6,大概是2236。所以不能枚举四个位置(时间复杂度太高),最多只能枚举两个位置。所以可以考虑先枚举两个位置(c,d)的结果并存储,然后再从小枚举另外两个(a,b)的位置,这样保证a<b,c<d,再枚举a,b时用t=n-c\*c-d\*d暂存结果,二分之前存储的c,d查看是否有合适的结果。


代码:


细节的地方在于2500010,2500010怎么来的呢?我们可以查看sum是如何增加的,可以看到,sum[m++]的位置在c,d的循环里面,c循环的次数为根号n,d的循环次数小于根号n,sum增加的次数大概为根号n,根号n-1,根号n-2...3,2,1,求和后大概为n/2。




#include<bits/stdc++.h>

using namespace std;

struct Sum{
int c;
int d;
int sum;
bool operator< (const Sum &t)const{
if(sum!=t.sum) return sum<t.sum;
if(c!=t.c) return c<t.c;
return d<t.d;
}
}sum[2500010];
int n;
int m=0;
int main(){
cin>>n;
for(int c=0;cc<=n;c++){
for(int d=c;c
c+dd<=n;d++){
sum[m++] = {c,d,c
c+d*d};
}
}
sort(sum,sum+m);

for(int a = 0;a*a<=n;a++){
    for(int b=a;a*a+b*b<=n;b++){
        int t = n-a*a-b*b;
        int l=0,r = m-1;
        while(l<r){
            int mid = l+r>>1;
            if(sum[mid].sum>=t) r = mid;
            else l = mid+1;
        }
        if(sum[l].sum == t){
            cout<<a<<" "<<b<<" "<<sum[l].c<<" "<<sum[l].d<<endl;
            return 0;
        }
    }
}
return 0;

}



#### 3.分巧克力


题目连接:[1227. 分巧克力 - AcWing题库]( )


题意:给n块h[i]\*w[i]边长的巧克力,要分给k个小朋友,如何切巧克力让巧克力边长最大,并能分给所有的小朋友。


解题思路:


题目要求找巧克力边长最大,假如边长增大,那么可以切出来的巧克力块数会变少。所以想要满足分给所有的小朋友,边长较小的区间都是可以的,为左区间,左区间的右端点就是答案。


代码:  
 细节之处在于,h[i]\*w[i]边长的巧克力对于切mid边长的巧克力,能切多少块呢?答案是h[i]/mid下取整\*w[i]/mid下取整。



#include<bits/stdc++.h>

using namespace std;

结尾

学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

数会变少。所以想要满足分给所有的小朋友,边长较小的区间都是可以的,为左区间,左区间的右端点就是答案。

代码:
细节之处在于,h[i]*w[i]边长的巧克力对于切mid边长的巧克力,能切多少块呢?答案是h[i]/mid下取整*w[i]/mid下取整。

#include<bits/stdc++.h>

using namespace std;


### 结尾

学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**

![html5](https://img-blog.csdnimg.cn/img_convert/156bb470b67924b2bde8ce2cec915c1a.webp?x-oss-process=image/format,png)
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值