问题标题: 刚回归

0
0
已解决
蔡俊豪
蔡俊豪
高级守护
高级守护

谁有什么笔记或者是讲义吗,只要不关于cout什么就行,基本上啥都忘了,豆子可以加到500


1
已采纳
高驰宇
高驰宇
初级光能
初级光能

H2-v4-阶段3-课时1-二分基础-课后讲义

二分基础

一、二分查找的原理

       在一个单调序列中,如果我们要查找某个元素T是否存在。我们每次查找失败,可以帮助我们排除掉这个序列剩余部分中一半的元素。直到我们查找成功,或者发现序列中不存在元素T。

二、二分查找的过程

以一个含10个元素的从小到大排序的数组为例,在数组中查找34的位置:
先确定数组中间位置mid
image.png
a[mid]=8<34,所以可以排除左半边数组,更新left=mid+1

接着确定剩余数组的中间位置mid
image.png
a[mid]=36>34,所以可以排除右半边数组,更新right=mid-1

再确定剩余数组的中间位置mid
image.png
a[mid]=9<34,所以可以排除左半边数组,更新left=mid+1

image.png
此时,left==right,a[mid]=34,找到了34所在的位置。

如果数组当中没有34,则最后一步查找会更新left或right的值,使得left>right,表示没有查找到。

三、二分查找的代码实现

       写二分查找时,分为3步,按照这个顺序来写。其余部分为框架,照抄即可。

  1. 确定要排除哪部分
  2. 怎么排除
  3. mid如何取值
 

int left = l, right = r; while(left<right) { mid = ____3____; if( ____1____ ) ____2____; else ____2____; }

mid的取值其实有两种方法,一种是
    mid = (left+right)/2
一种是
    mid = (left+right+1)/2
两种取法只是在left+right的和为奇数时,中点取到的位置不同。
    前者在左边,后者在右边。
    但是不会影响每次区间的正确缩小,直到left和right相邻。

如果存在 right=mid,中点mid也取在右边,则执行right=mid时,right不会移动,从而死循环。
这种情况mid只能取在左,即 mid=(left+right)/2

同理,如果存在 left=mid,中点mid也取在左边,则执行left=mid时,left不会移动,从而死循环。
这种情况mid只能取在右,即 mid=(left+right+1)/2

为了防止溢出,通常mid还写成 left+(right-left)/2 以及 left+(right-left+1)/2 这两种形式。

1. 在单调递增的有重复元素的数组a中查找元素k,找到则返回k第一次出现时的位置,找不到则返回-1

 

int BinarySearch(int a[], int k, int l, int r) { int left = l, right = r, mid; while(left<right) { //注意不能写等号,否则可能死循环 mid = left+(right-left)/2; if(a[mid]<k) left = mid+1; else right = mid; } if(a[right]==k) return right; return -1; }

2. 在单调递增的有重复元素的数组a中查找元素k,找到则返回k最后一次出现时的位置,找不到则返回-1

 

int BinarySearch(int a[], int k, int l, int r) { int left = l, right = r, mid; while(left<right) { //注意不能写等号,否则可能死循环 mid = left+(right-left+1)/2; if(a[mid]<=k) left = mid; else right = mid-1; } if(a[left]==k) return left; return -1; }

课堂练习

8018 找数游戏1

 

#include<iostream> #include<cstdio> using namespace std; int n,m,a[100005],t; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } while(m--){ scanf("%d",&t); int L=1,R=n,ans=-1; while(L<=R){ int mid=(L+R)/2; if(a[mid]==t){ ans=mid; //第一次出现,取左边 R=mid-1; }else if(a[mid]>t){//太大了,在左半部分 R=mid-1; }else{ L=mid+1; } } printf("%d\n",ans); } return 0; }

8019 找数游戏2

 

#include<iostream> #include<cstdio> using namespace std; int n,m,a[100005],t; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); } while(m--){ scanf("%d",&t); int L=1,R=n,ans=-1; while(L<=R){ int mid=(L+R)/2; if(a[mid]==t){ ans=mid; //最后一次出现,取右边 L=mid+1; }else if(a[mid]>t){//太大了,在左半部分 R=mid-1; }else{ L=mid+1; } } printf("%d\n",ans); } return 0; }

2784 重合

 

/* 2784 重合 两个长度为n m的数组 求两个数组的重合元素(按照第一个数组的顺序输出) 【样例】 4 3 2 15 6 8 8 9 2 准备工作:对数组b进行排序 思路:遍历第一个数组(按1的顺序) ——>如果双重循环,超时了 先对b进行排序*** 将a[i]在b数组中进行查找——>二分 */ #include<iostream> #include<algorithm> using namespace std; int n,m; int a[100005],b[100005]; int main(){ cin>>n>>m; for(int i=1;i<=n;i++){ cin>>a[i]; } for(int i=1;i<=m;i++){ cin>>b[i]; } //对b数组排序 mlogm sort(b+1,b+m+1); //遍历第一个数组,按照第一个数组的顺序输出 for(int i=1;i<=n;i++){ int x=a[i]; int L=1,R=m; //对b数组进行操作 nlogm while(L<=R){ int mid=(L+R)/2; if(b[mid]==x){ cout<<x<<" "; break; }else if(b[mid]>x){//太大了,取左 R=mid-1; }else{ L=mid+1; } } } //——>总时间复杂度O((m+n)(logm)) 比O(n+n)小 return 0; }

5082 完全平方数

 

#include<iostream> using namespace std; int n; int main(){ cin>>n; bool flag=0; int L=1,R=min(n,100); while(L<=R){ int mid=(L+R)/2; if(mid*mid==n){ flag=1; break; }else if(mid*mid<n){ L=mid+1;//小了,取大的部分 }else{ R=mid-1;//大了,取小的部分 } } if(flag==1){ cout<<"YES"; }else{ cout<<"NO"; } return 0; }

1
高驰宇
高驰宇
初级光能
初级光能

H2-v4-阶段3-课时5-迷宫问题-课后讲义

本节课思维导图

一、搜索与回溯

 
  搜索与回溯是计算机解题中常用的算法,很多问题无法根据某种确定的计算法则来求解,但是可以利用搜索与回溯的技术求解。
  回溯是搜索算法中的一种控制策略。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前探索,在探索过程中,一旦发现原来的选择是错误的,就退回一步重新选择,继续向前探索,如此反复进行,直至得到解或证明无解。
 

二、回溯法框架

 

void dfs(int k) { if(到目的地) 输出解 //边界条件 else for (i=1;i<=算符总数;i++) //枚举所有可能性 if (满足条件){ //约束条件 保存结果和状态 dfs(k+1); //递归搜索 恢复保存结果之前的状态 //状态恢复 } } }

三、迷宫问题

  我们现在来看一下经典的迷宫问题。
  在一方形迷宫中有n*m个格子,我们知道闯迷宫的人的初始坐标 (sx,sy) 以及目标出口 (tx,ty),以及迷宫中若干障碍物的坐标。每次我们可以向上、下、左、右的格子做一次移动,让我们判断是否能够到达迷宫出口,或者统计到达迷宫出口的方案数。

用void dfs(int x,int y)表示走到(x,y)格子。

1. 边界条件(递归的终点)

  当到达出口 (tx,ty) 时结束

 

if(x==tx && y==ty) { 输出解/计数/标记; return; }

2. 枚举所有可能性

  从(x,y)往上、下、左、右分别探索

 

dfs(x-1,y); dfs(x+1,y); dfs(x,y-1); dfs(x+1,y-1);

  或

 

int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}}; for(int i=0;i<4;i++){ int nx=x+dir[i][0],ny=y+dir[i][1]; dfs(nx,ny); }

3. 约束条件

①不能走到障碍处

  这可通过输入的地图信息来判断。比如将地图信息用一个二维数组map[x][y]来存储,map[x][y] = 1表示障碍,map[x][y] = 0表示道路。

②不能走出地图边界

  如果地图的大小为m*n,且左上角格子下标为(1,1),则到达的坐标(x,y)要满足1<=x<=m, 1<=y<=n

③已经走过的位置不能再走

  已经走过的位置我们可以通过一个全局的二维数组vis[x][y]来记录。其中可以用vis[x][y] = 1表示(x, y)这个位置已经走过,vis[x][y] = 0表示(x, y)这个位置未走过。

 

if(map[x][y]==0 && vis[x][y]==0 && x>=1 && x<=m && y>=1 && y<=n){ (x,y)可以走; }

四、课堂练习

1401 迷宫

 

/* 1401 迷宫 n*m的迷宫 T个障碍物 起点——>终点的方案数 每个方格最多经过1次——>同一个路线,走过的不能走 规则:上右下左四个方向,按顺序去查找 【样例】 3 3 2 1 1 3 1 1 3 2 1 结果为2 注意:①回到起点之后结束了吗? ——>没有,起点的四个方向都判断完才结束 ②什么时候回溯? 1、走到终点 2、无路可走 方向数组 */ #include<iostream> using namespace std; int n,m,t,cnt; int sx,sy,ex,ey; int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}}; bool g[10][10];//标记是不是障碍物 bool vis[10][10];//标记有没有走过 void dfs(int x,int y){ //边界 if(x==ex&&y==ey){ cnt++; return ; } //遍历四个方向,判断能不能走 for(int i=0;i<4;i++){ int nx=x+dir[i][0]; int ny=y+dir[i][1]; //判断可不可以走 if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&g[nx][ny]==0&&vis[nx][ny]==0){//未越界&&不是障碍物&&没走过 //递归之前进行标记 vis[nx][ny]=1; dfs(nx,ny); //回溯——>撤销标记 vis[nx][ny]=0; } } //无路可走 return ; } int main(){ cin>>n>>m>>t; cin>>sx>>sy>>ex>>ey; //输入障碍物 while(t--){ int u,v; cin>>u>>v; g[u][v]=1; } //起点最开始要被标记******* vis[sx][sy]=1; dfs(sx,sy); cout<<cnt; return 0; }

4956 棋盘(2)

 

#include<iostream> using namespace std; int n,t; bool flag; bool g[10][10];//表示可不可以走 1表示可以走 bool vis[10][10];//标记走没走过 int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}}; void dfs(int x,int y){ //剪枝优化 if(flag==true){ return ; } //边界——>走到终点 if(x==n&&y==n){ flag=1; return ; } //遍历四个方向 for(int i=0;i<4;i++){ int nx=x+dir[i][0]; int ny=y+dir[i][1]; //未越界&&可以走&&没走过 if(nx>=1&&nx<=n&&ny>=1&&ny<=n&&g[nx][ny]==1&&vis[nx][ny]==0){ vis[nx][ny]=1; dfs(nx,ny); vis[nx][ny]=0; } } //无路可走 return ; } int main(){ cin>>n>>t; while(t--){ int a,b,c; cin>>a>>b>>c; g[a][b]=1; } //起点标记 vis[1][1]=1; dfs(1,1); cout<<flag; return 0; }

3020 道路施工

 

#include<bits/stdc++.h> using namespace std; int a,b,N,cnt,x,y; int dir[4][2]={{1,0},{0,1}}; bool g[20][20];//是否有障碍物 bool vis[20][20];//是否是回头路 void dfs(int x,int y){ if(x==a&&y==b){ cnt++; return ; } for(int i=0;i<2;i++){ int dx=x+dir[i][0],dy=y+dir[i][1]; if(dx>=1&&dx<=a&&dy>=1&&dy<=b&&!vis[dx][dy]&&!g[x][y]){ vis[dx][dy]=true; dfs(dx,dy); vis[dx][dy]=false; } } return ; }

1
高驰宇
高驰宇
初级光能
初级光能

H2-v4-阶段3-课时4-实数域上的二分及二分综合应用-课后讲义

本节课思维导图

一、 实数域上的二分

  有些情况中,我们需要在某个范围中查找一个小数值。并且如果答案具有单调性,则我们可以使用二分查找。

  首先在实数域上的二分查找中,我们不能再以l等于r作为循环的终止条件。因为两个小数不能准确判断相等。所以我们改用l和r的距离小于精确度eps来终止循环。这时l和r的更新直接更新到mid位置即可。

  当精确到小数点后k位时,eps通常取10-(k+2)。

二、 课堂题代码

2886 Pie

 

/* 分析题意: 有n个派,每个派都有一个半径,每个派都是一个高为1 有p个朋友,加上自己有p+1个人 每个人都会一块派但不能 几个派的小块拼成 问:每个人拿到的派最大是多少? 分析: 派是圆柱型,高度都是1,那么计算的时候只要算面积就可以了 人数:p+1 解x可能是一个小数 x越大分的人数越少 范围: 0 - 最大的一块派面积 问题单调性: 假设x是问题的解, 如果x是正确的,要寻求最大的解,可以排除x以下 如果x不是正确的,那么x以上的也不行,排除x以上 所以分析问题是单调的 思路: 求解转判定,利用二分来查找 */ #include<iostream> using namespace std; double a[10005],pi=3.141592653589; double maxn=-1,eps=1e-5; int n,f,r; bool check(double mid){ int cnt=0; for(int i=1;i<=n;i++){ cnt+=(int)(a[i]/mid); } return cnt>=f+1;//切的块数大于等于人数 } int main(){ cin>>n>>f; for(int i=1;i<=n;i++){ cin>>r; a[i]=pi*r*r;//计算面积 maxn=max(maxn,a[i]);//上限是最大值 } double l=0,r=maxn; while(l+eps<r){ double mid=(l+r)/2; if(check(mid)){ l=mid; }else{ r=mid; } } printf("%.3f",l); return 0; }

额外知识补充

可以看下9163这个题,假如用当前的这个思路去做

然后测试这个样例
577862887 373 696573
会发现最后会产生死循环

通过调试发现后面的值是不变化的,主要就是eps的精度不够,加上基本没变化,就会陷入死循环,这个时候需要换一种方式去解决

2780 月利率

 

/* 一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款 要求计算出最大利率,可以在规定的日期还完 分析样例: 1000 100 12 总钱数 1000 ,每个月还100,12月还完 将利率2.9带入后,看下过程 月份 本月的本金 本月贷款 还款金额 下月的本金 1 1000 1029 100 929 2 929 955.941 100 855.941 3 855.941 880.763 100 780.763 4 780.763 803.405 100 703.405 5 703.405 723.804 100 623.804 6 623.804 641.895 100 541.895 7 541.895 557.609 100 457.609 8 457.609 470.88 100 370.88 9 370.88 381.636 100 281.636 10 281.636 289.803 100 189.803 11 189.803 195.307 100 95.307 12 95.307 98.0713 100 -1.92872 最后第12月还完的金额是负数,说明还完了,这个解是正确的 可以推算出: 利率越高,还的时间越长 分析问题的单调性 假设 x是问题的解 如果x是正确的,要寻求最大的,可以排除x以下 如果x不是正确的,那么x以下的也不行,排除x以上的 解的范围是: 0 - 极限的10是绝对不行的 */ #include<iostream> using namespace std; double s,p,m,eps=1e-5; bool f(double mid){ double t=s;//总钱数 for(int i=1;i<=m;i++){ t=t*(1+mid);//当月的贷款 t-=p;//还完剩下的 } return t<=0;//是否能还完 } int main(){ cin>>s>>p>>m; double l=0,r=10;//利率是10 就还不完了 while(l+eps<r){ double mid=(l+r)/2; if(f(mid)){ l=mid; }else{ r=mid; } } printf("%.1f",int(l*1000+0.5)/10.0); return 0; }

10272 接水

 

/* 分析: 水龙头越多,接水时间越少 枚举范围: 1 - n(人数是n) 分析问题的单调性: 假定x是问题的解 如果x是正确的,要寻求小的解,可以排除x以上 如果x不是正确的,那么x小的必然不行,可以排除x以下的 排队的策略: 当轮到第i个人接水,需要找排队时间最少的水龙头 */ #include<iostream> #include<algorithm> #include<cstring> using namespace std; int n,x,t[15],b[15];//t:第i个人的接水时间 b:水龙头接水时间 bool f(int mid){ memset(b,0,sizeof(b)); for(int i=1;i<=n;i++){//第i个人接水 //选择排队最少的那个 sort(b+1,b+mid+1); b[1]+=t[i];//第一个排队时间最少 } int maxn=-1; for(int i=1;i<=mid;i++){ maxn=max(maxn,b[i]); } return maxn<=x; } int main(){ cin>>n>>x; for(int i=1;i<=n;i++){ cin>>t[i]; } int l=1,r=n,ans; while(l<=r){ int mid=(l+r)/2; if(f(mid)){ ans=mid; r=mid-1; }else{ l=mid+1; } } cout<<ans; return 0; }

0
王子墨
王子墨
高级光能
高级光能

别发这种帖子,会被举报,这个不能发的,快采纳吧

0
0
0
蔡俊豪
蔡俊豪
高级守护
高级守护

有没有简单点的,我现在得从头学了

 

0
高驰宇
高驰宇
初级光能
初级光能

哪种,是不是for循环、while循环的层次,还是位运算那种?

我要回答