本文是期末对于周春樵老师算法基础课的复习与总结,ppt为周老师上课课件,如有侵权,请联系删除。
文章目录
1.绪论
常用的时间复杂度比较
这里cin.getline(b,100).表示读取一行,将最多100个字符(包含\0)读入到字符数组b中。
课堂作业
作业代码
1.高精度加法(已经去除前导零)
/*
高精度加法:可以进行maxn位的加法运算
*/
#include <bits/stdc++.h>
using namespace std;
const int maxn=200;
int a[maxn],b[maxn];//a数组存放被加数,b数组存放加数
string s1,s2;
int main()
{
cin>>s1>>s2;//读入两个字符串
int len1=s1.length(),len2=s2.length();
for(int i=0;i<len1;i++) a[i]=s1[len1-i-1]-'0'; //倒序,从个位开始存
for(int i=0;i<len2;i++) b[i]=s2[len2-i-1]-'0';
int len3=max(len1,len2);//取两者最大的长度
for(int i=0;i<len3;i++)
{
a[i]+=b[i];
if(a[i]>9) a[i]-=10,a[i+1]++;
}
while(a[len3]==0) len3--;//去掉高位的0
for(int i=len3;i>=0;i--) cout<<a[i]; //倒序输出
return 0;
}
2. a的b次幂取模
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int a,b;
scanf("%d%d",&a,&b);
int n=a;
for(int i=1;i<b;i++) n=(n*a)%1000;//每次运算保留后三位
printf("%d",n);
return 0;
}
2.枚举
水仙花数是指一个 3 位数,它的每个位上的数字的 3次幂之和等于它本身(例如:1^3 + 5^3+ 3^3 = 153)。
c语言代码
#include <stdio.h>
int main()
{
int i,a,b,c;
for (i=100; i<=999; i++)
{
a=i/100;
b=i/10%10;
c=i%10;
if (a*a*a+b*b*b+c*c*c==i) printf("%d\n",i);
}
return 0;
}
#include <stdio.h>
int main( )
{
char A,B,C;
for (A='X'; A<='Z'; A++)//枚举A,B,C的取值
for (B='X'; B<='Z'; B++)
for (C='X'; C<='Z';C++)
if ( A!='X' && C!='X' && C!='Z' && A!=B && A!=C && B!=C ) printf("A=%c,B=%c,C=%c\n",A,B,C);
return 0;
}
参考代码
#include <stdio.h>
int main()
{ int a,b,c,d;
for (a=1;a<=4;a++)
for (b=1; b<=4; b++)
for (c=1; c<=4; c++)
{ d=10-a-b-c;
if ( (c==1)!=(b==2) && (c==2)!=(d==3)
&& (a==2)!=(d==4) && a!=b && a!=c
&& a!=d && b!=c && b!=d && c!=d )
printf("%d,%d,%d,%d\n",a,b,c,d); }
return 0;
}
参考代码:运行到第5个便超时了。
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n=1,sum=0,count=0;
while(count<3)//完全数的个数
{
sum=0;
for(int i=1;i<n;i++) if(n%i==0) sum+=i;//累加因数
if(n==sum) //如果找到完全数
{
cout<<n<<endl;
count++;//个数+1
}
n++;
}
return 0;
}
课堂作业
作业代码
1 三进制数遍历
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int a1,a2,a3,a4,a5,a6;
int L,R,count=0;
cin>>L>>R;
for(a1=0;a1<=2;a1++)//遍历6位3进制数
for(a2=0;a2<=2;a2++)
for(a3=0;a3<=2;a3++)
for(a4=0;a4<=2;a4++)
for(a5=0;a5<=2;a5++)
for(a6=0;a6<=2;a6++)
{
int sum=a1+a2+a3+a4+a5+a6;//三进制下进行每位求和
if(sum>=L && sum<=R || sum==2 || sum==3 || sum==5 || sum==7 || sum==11) count++;
}
cout<<count;
cout<<endl<<pow(3,6)<<endl; //3^6等于729
return 0;
}
2.6个砝码的组合数
#include <iostream>
using namespace std;
int i,a1,a2,a3,a4,a5,a6,b1,b2,b3,b4,b5,b6,count=0;
bool w[1001];//砝码重量数组
int main()
{
cin>>a1>>a2>>a3>>a4>>a5>>a6;//分别输入1g,2g,3g,5g,10g,20g砝码数量
for (i=0;i<1001;i++) w[i]=false;
for (b1=0;b1<=a1;b1++)
for (b2=0;b2<=a2;b2++)
for (b3=0;b3<=a3;b3++)
for (b4=0;b4<=a4;b4++)
for (b5=0;b5<=a5;b5++)
for (b6=0;b6<=a6;b6++)
w[b1+b2*2+b3*3+b4*5+b5*10+b6*20]=true;
for (i=1;i<1001;i++) if (w[i]==true) count++;
cout<<"Total="<<count;
return 0;
}
3.简单计算与模拟
模拟的基本思想
(1)用模拟法解决问题的基本思想是对事物进行抽象,将现实世界的事物映射成计算机所能识别的代码符号,而将现实事物之间的关系映射成运算或逻辑控制流。
(2)模拟法是编程的基本功,没有复杂的公式和技巧,只要读懂题目,照着逻辑,理对代码,基本都可以完成。
例1:鸡兔同笼
问题描述
一个笼子里面关了鸡和兔子(鸡有2只脚,兔子有4只脚,没有例外)。已经知道了笼子里面脚的总数a,问笼子里面至少有多少只动物,至多有多少只动物。
输入格式
第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,每行一个正整数a (a < 32768)
输出
输出包含n行,每行对应一个输入,包含两个正整数,第一个是最少的动物数,第二个是最多的动物数,两个正整数用一个空格分开,如果没有满足要求的答案,则输出两个0。
样例输入
2
3
20
样例输出
0 0
5 10
参考代码
#include <cstdio>
int main()
{
int nCase,nFeets;// 脚数
scanf("%d",&nCase);//读入测试组数
int i;
for(i=0;i<nCase;i++)
{
scanf("%d",&nFeets);
if(nFeets%4==0) printf("%d %d\n",nFeets/4,nFeets/2);
else if(nFeets%2==0) printf("%d %d\n",nFeets/4+1,nFeets/2);
else printf("0 0\n");
}
return 0;
}
例2:校门外的树
问题描述
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止
输入数据
输入的第一行有两个整数L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
输出要求
输出包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
输入样例
500 3
150 300
100 200
470 471
输出样例
298
参考代码
思路:如果区域存在,则该区域内都用1覆盖,最后计算0的个数即可。
#include <cstdio>
#include <cstring>
int main()
{
int m,n,l,k,i,count=0;
int a[10001];
memset(a,0,sizeof(a));
scanf("%d%d",&m,&n);
while(n--)
{
scanf("%d%d",&l,&k);
for(i=l;i<=k;i++) a[i]=1;
}
for(i=0;i<=m;i++) if(a[i]==0) count++;
printf("%d\n",count);
return 0;
}
例3:乒乓球对决
问题描述
华华通过以下方式进行分析,首先将比赛每个球的胜负列成一张表,然后分别计算在11分制和21分制下,双方的比赛结果(截至记录末尾)。
比如现在有这么一份记录,(其中W表示华华获得一分,L表示华华对手获得一分):
WWWWWWWWWWWWWWWWWWWWWWLW
在11分制下,此时比赛的结果是华华第一局11比0获胜,第二局11比0获胜,正在进行第三局,当前比分1比1。而在21分制下,此时比赛结果是华华第一局21比0获胜,正在进行第二局,比分2比1。如果一局比赛刚开始,则此时比分为0比0。直到分差大于或者等于2,才一局结束。
你的程序就是要对于一系列比赛信息的输入(WL形式),输出正确的结果。
输入格式
每个输入文件包含若干行字符串,字符串有大写的W、L和E组成。其中E表示比赛信息结束,程序应该忽略E之后的所有内容。
输出格式
输出由两部分组成,每部分有若干行,每一行对应一局比赛的比分(按比赛信息输入顺序)。其中第一部分是11分制下的结果,第二部分是21分制下的结果,两部分之间由一个空行分隔。
输入样例
WWWWWWWWWWWWWWWWWWWWWWLWE
输出样例
11:0
11:0
1:1
21:0
2:1
解题思路
1、可通过交叉模拟实现;
2、根据题目定义两个变量sw,sl来表示选手的每局得分;
3、在11分制与21分制两种情况下遍历“得分字符串”;
4、以11分制为例:只要满足sw=11或者sl=11且此时sw与sl的绝对值差值大于等于2的条件,就输出sw:sl;
5、输出比分后一定要将sw与sl清零,因为要开始计算下一局比分,按照此方法一直计算下去直到遇到结束字符‘E’位置,此时判断一下sw与sl是否为0,若不为0,则输出sw:sl。处理21分制与上述方法相同。
参考代码
#include <bits/stdc++.h>
using namespace std;
char c,s[100001];//c用来读取单个字符,s用来存储整个字符串
int sw,sl,cnt;
void print_score(int n)//n表示
{
sw=sl=0;//sw表示华华的胜场,sl表示对手的胜场
for(int i=0;i<cnt;i++)//遍历整个s字符数组
{
if(s[i]=='W') sw++;
if(s[i]=='L') sl++;
if((sw>=n || sl>=n) && (int)abs(sw-sl)>=2) //达到分数,并且要求分差 ≥2
{
cout<<sw<<":"<<sl<<endl;//输出
sw=sl=0;//分辨一句,清空胜场数,方便下一场的统计
}
}
if(sw||sl) cout<<sw<<":"<<sl;//输出最后还未完成的比分
}
int main()
{
// ios::sync_with_stdio(false);
cnt=0;
while(cin>>c)//逐个字符读入
{
if(c=='E') break;
else s[cnt++]=c;//存放在数组s中
}
print_score(11);
cout<<endl<<endl;
print_score(21);
return 0;
}
例4:均分纸牌
问题描述
有N堆纸牌,编号分别为 1,2,…,N。每堆上有若干张,但纸牌总数必为N的倍数。可以在任一堆上取若干张纸牌,然后移动。
移牌规则为:在编号为1堆上取的纸牌,只能移到编号为2的堆上;在编号为N的堆上取的纸牌,只能移到编号为N-1的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如N=4,4堆纸牌数分别为:
①9②8③17④6
移动3次可达到目的:
从 ③ 取4张牌放到 ④ (9,8,13,10)-> 从 ③ 取3张牌放到 ②(9,11,10,10)-> 从 ② 取1张牌放到①(10,10,10,10)。
输入格式
两行
第一行为:N(N堆纸牌,1≤N≤100)
第二行为:A1,A2…An(N堆纸牌,每堆纸牌初始数,1≤Ai≤10000)
输出格式
一行:即所有堆均达到相等时的最少移动次数。
输入样例
4
9 8 17 6
输出样例
3
可以看成移动负数!
如果想到把每堆牌的张数减去平均张数,题目就变成移动正数,加到负数中,使大家都变成0。拿例题来说,平均张数为10,原张数9,8,17,6,变为-1,-2,7,-4,其中没有为0的数,我们从左边出发:要使第1堆的牌数-1变为0,只须将-1张牌移到它的右边(第2堆)-2中;结果是-1变为0,-2变为-3,各堆牌张数变为0,-3,7,-4;同理:要使第2堆变为0,只需将-3移到它的右边(第3堆)中去,各堆牌张数变为0,0,4,-4;要使第3堆变为0,只需将第3堆中的4移到它的右边(第4堆)中去,结果为0,0,0,0,完成任务。解释参考:https://www.cnblogs.com/zzyh/p/6641935.html
参考代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=105;
int in[maxn],n,sum=0,cnt=0;
int main()
{
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<n;i++)
{
cin>>in[i];//读入
sum+=in[i];//累加
}
int tmp=sum/n;//平均值
for(int i=0;i<n;i++) in[i]-=tmp;//取差值
for(int i=0;i<n-1;i++)
{
if(in[i]!=0)
{
in[i+1]+=in[i];
cnt++;
}
}
cout<<cnt;
return 0;
}
作业题
习题1:与7无关的数
问题描述
一个正整数,如果它能被7整除,或者它的十进制表示法中某一位上的数字为7,则称其为与7相关的数.现求所有小于等于n(n < 100)的与7无关的正整数的平方和。
输入
输入为一行,正整数n(n < 100)
输出
输出一行,包含一个整数,即小于等于n的所有与7无关的正整数的平方和。
样例输入
21
样例输出
2336
习题2:蜜蜂繁衍
问题描述
在非洲,有一个非常特殊的蜂种。每年,这个物种的一只雌性蜜蜂会生育一只雄性蜜蜂,而一只雄性蜜蜂会生育一只雌性蜜蜂和一只雄性蜜蜂,生育后它们都会死去。
现在科学家们意外地发现了这一特殊物种的一个“神奇的雌蜂”,她是不死的,而且仍然可以象其他雌蜂一样每年生育一次。科学家想知道在n年后会有多少蜜蜂。请写一个程序,帮助他们计算n年后雄蜂的数量和所有的蜜蜂总数。
输入
输入一个正整数n(n>=0)
输出
输出的每行有两个数字,第一个数字是n年后雄蜂的数量,第二个数字是n年后蜜蜂的总数(这两个数字不会超过232)
输入样例
2
输出样例
2 4
作业代码
#include <cstdio>
#include<iostream>
using namespace std;
int main()
{
int n,i,count=0;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
if(i%7!=0) //输入数据i不能被7整除
{
if(i/10!=7 && i%10!=7) //输入数据i的十位和个位不含有数字7
{
count+=i*i;
cout<<i<<" "; //
}
}
}
printf("%d",count);
return 0;
}
蜜蜂存在递推关系
第n年的雄蜂=前一年的雌蜂+雄蜂数量之和
第n年的雌蜂=1+前一年的雄蜂
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n,a,b;//a表示雌蜂的数量,初始值为1;b表示雄峰的数量,初始值为0
int main()
{
ios::sync_with_stdio(false);
cin>>n;
a=1,b=0;
int c,d;
for(int i=0;i<=n-1;i++)
{
c=b+1;//一年后雌蜂的数量等于前一年雄峰的数量+1
d=a+b;//一年后雄峰的数量等于前一年蜜蜂的总和
a=c;
b=d;
}
cout<<b<<' '<<a+b;
return 0;
}
4.递归
例1:逆波兰式
问题描述
逆波兰表达式是一种把运算符前置的算术表达式,例如普通的表达式2 + 3 的逆波兰表示法为+ 2 3。逆波兰表达式的优点是运算符之间不必有优先级关系,也不必用括号改变运算次序,例如(2 + 3) * 4 的逆波兰表示法为* + 2 3 4。本题求解逆波兰表达式的值,其中运算符包括 + - * / 四个。
输入数据
输入为一行,其中运算符和运算数之间都用空格分隔,运算数是浮点数
输出要求
输出为一行,表达式的值。
输入样例
* + 11.0 12.0 + 24.0 35.0
输出样例
1357.000000
样例分析:计算位(11.0+12.0)*(24.0+35.0)=1357.000000
参考程序
#include <stdio.h>
#include <math.h>
char a[20];//字符数组
double polan()
{
scanf("%s",a);
switch(a[0])
{
case '+':return polan()+polan();
case '-':return polan()-polan();
case '*':return polan()*polan();
case '/':return polan()/polan();
default:return atof(a); //把字符串转换为浮点数
}
}
int main()
{
printf("%lf",polan());//读入无符号浮点数
return 0;
}
例2:汉诺塔
问题描述
古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上(如图)。有一个和尚想把这64个盘子从A座移到C座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。在移动过程中可以利用B座,要求输出移动的步骤。
输入数据
汉诺塔内的盘子个数n(1<=n<=64)。
输出要求
输出移动的步骤,每步一行,如从A座移动到C座,输出”A->C”。
样例输入
3
样例输出
A->C
A->B
C->B
A->C
B->A
B->C
解题思路
采用递归思路来分析,把原问题分解成一个或多个形式相同、但规模小一些问题。具体分为三个步骤:
(1)将A座上n-1个盘子以C座为中转,移动到B座。
(2)把A座最下面的一个盘子移动到C座。
(3)将B座上的n-1个盘子以A座为中转,移动到C座。
上面的(1)和(3)两个步骤和原问题形式相同,只是规模少了1(要处理的盘子数模少了1)。当然,如果n=1,即把A座上的一个盘子移动到C座。
另外可以知道,汉诺塔移动步数和规模n有关,移动步数=2^n-1
#include <iostream>
#include<cmath>
using namespace std;
int cnt=0;
//将src座上的n个盘子以mid座为中转,移动到dest座
void Hanoi(int n,char src,char mid,char dest)
{
if(n==1)
{
cout<<src<<"->"<<dest<<endl;//只有一个盘子,直接从src移到dest
return;
}
//先将n-1个盘子从src移动到mid
Hanoi(n-1,src,dest,mid);
//再将一个盘子从src移动到dest
cout<<src<<"->"<<dest<<endl;
//将n-1个盘子从mid移动到dest
Hanoi(n-1,mid,src,dest);
return;
}
int main()
{
int n;
//char src,mid,dest;
cin>>n; //输入盘子数模
Hanoi(n,'A','B','C');
cout<<(pow(2,n)-1)<<endl;//输出汉诺塔的步数
return 0;
}
例3:二叉树最近公共祖先
如下图所示,由正整数1, 2, 3, …组成了一棵无限大的满二叉树。从某一个结点到根结点(编号是1的结点)都有一条唯一的路径,比如从10到根结点的路径是(10, 5, 2, 1),从4到根结点的路径是(4, 2, 1),从根结点1到根结点的路径上只包含一个结点1,因此路径就是(1)。
对于两个结点x和y,假设他们到根结点的路径分别是(x1, x2, … ,1)和(y1, y2, … ,1)(这里显然有x = x1,y = y1),那么必然存在两个正整数i和j,使得从xi 和 yj开始,有xi = yj , xi + 1 = yj + 1, xi + 2 = yj + 2,… 现在的问题就是,给定x和y,要求xi(也就是yj)。
输入格式
输入只有一行,包括两个正整数x 和y,这两个正整数都不大于1000
输出要求
输出只有一个正整数xi
输入样例
10 4
输出样例
2
解题思路
这个题目要求树上任意两个节点的最近公共子节点。分析这棵树的结构不难看出,不论奇数偶数,每个数对2 做整数除法,就走到它的上层结点。
我们可以每次让较大的一个数(也就是在树上位于较低层次的节点)向上走一个结点,直到两个结点相遇。
如果两个节点位于同一层,并且它们不相等,可以让其中任何一个先往上走,然后另一个再往上走,直到它们相遇。
设common(x, y)表示整数x 和y的最近公共子节点,那么,根据比较x 和y 的值,可以得到三种情况:
(1) x 与y 相等,则common(x, y)等于x 并且等于y;
(2) x 大于y,则common(x, y)等于common(x/2, y);
(3) x 小于y,则common(x, y)等于common(x ,y/2);
#include <stdio.h>
int n,m;
int common(int x,int y)
{
if(x==y) return x;
else if(x>y) return common(x/2,y);
else return common(x,y/2);
}
int main()
{
scanf("%d%d",&n,&m);
printf("%d",common(n,m));
return 0;
}
课堂作业
习题1:分解因数
问题描述
给出一个正整数a,要求分解成若干个正整数的乘积,即a=a1Xa2X…Xan,并且1<=a1<=…<=an,问这样的分解有多少种。注意:a=a也是一种分解。
输入数据
输入一组测试数据,测试数据包括一个正整数a(1<a<32768)。
输出要求
输出应是一个正整数,指明满足要求的分解的种数。
输入样例
20
输出样例
4
参考程序
#include <bits/stdc++.h>
using namespace std;
int n;
//fun返回值:分解的种数
int fun(int a,int b)//a代表要分解的数字,b代表(可能的)最大因子
{
if(a==1) return 1;//如果待分解的数是1,直接返回
if(b==1) return 0;//如果因子是1 ,返回0,表示不可分解
//如果b是a的因子
if(a%b==0) return fun(a/b,b)+fun(a,b-1);//使用该因子;不使用该因子两种情况
//否则把b-1作为最大因子
return fun(a,b-1);
}
int main()
{
scanf("%d",&n);
printf("%d",fun(n,n));//待分解的是n,最大因子也是n
return 0;
}
习题2:放苹果
问题描述
把 M 个同样的苹果放在N 个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K 表示)注意:5,1,1 和1,5,1 是同一种分法。
输入
输入一行包含两个整数M 和N,以空格分开。1<=M,N<=10
输出
用一行输出相应的答案
输入样例
7 3
输出样例
8
参考代码
#include <stdio.h>
int a,b;
int fun(int m,int n)//m代表苹果,n代表盘子
{
if(n==1 || m==0) return 1;//只有1个盘子或者苹果已经全部放完返回1
if(n>m) return fun(m,m);//当盘子数大于苹果数的时候,则必有至少n-m个盘子是空着的,n最大值取m
return fun(m-n,n)+fun(m,n-1);//每个盘子都有苹果的情况数+至少有一个盘子为空的情况数
//当每个盘子都有苹果的时候:f(m,n)相当于f(m-n,n)
}
int main()
{
scanf("%d%d",&a,&b);
printf("%d",fun(a,b));//a个苹果,b个盘子
return 0;
}
5.二分查找
相对于枚举算法,二分查找具有比较次数少,查找速度快,平均性能好的优点。缺点是要求待查的数据已被整理为有序列表。
二分查找的目标是对于有序列表,在其中寻找指定的目标元素的位置。其基本操作方式:将列表中间位置的元素与目标元素比较,如果两者相等,则查找成功;否则将列表从中间位置分开,分成前、后两个子列表,如果中间位置的元素大于目标元素,则进一步查找前一个子列表,否则进一步查找后一子列表。重复以上过程,直到找到满足条件的元素,使查找成功;或子表为空,此时指定元素不在列表内部。为了直观地理解二分查找的过程,下面给出具体的例子。
第一种情况:在单调递增序列S中查找大于等于t的数中的最小值(最大值最小化)
第二种情况:在单调递增序列S中查找小于等于t的数中的最大值(最小值最大化)
第三种情况:在实数域上二分(注意取值的精度)
例1:方程求根
问题描述
求下面方程的根:
f
(
x
)
=
x
3
−
5
x
2
+
10
x
−
80
=
0
f(x)=x^3-5x^2+10x-80=0
f(x)=x3−5x2+10x−80=0
输出要求
精确到小数点后9位。
输出样例
5.705085930
解题思路
参考代码
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
double BinarySearch(double right,double left)
{
double mid,f;
while(right-left>1e-11)
{
mid=left+(right-left)/2;
f=mid*mid*mid-5*mid*mid+10*mid-80;//将方程表示出来
if(f<0) left=mid;
else right=mid;
}
return left;
}
int main()
{
printf("%.9lf",BinarySearch(10,0.0));
return 0;
}
例2:绳子切分
问题描述
有N条绳子,它们长度分别为Li。如果从他们中切割出K条长度相同的绳子的话,这K条绳子每条最长能有多长?答案保留两位小数。
输入
第一行输入两个正整数N和K,1<=N<=10^4, 1<=K<=10^4
接下来输入N个浮点数Li表示每根绳子的长度,1<=Li<=10^5
输出
K条绳子的最大长度,答案保留2位小数
样例输入
4 11
8.02 7.43 4.57 5.39
样例输出
2.00
这里的x表示绳子的长度,C(x)表示切出来的绳子的条数。
#include <stdio.h>
const int maxn=1e4+10;
int n,k;
double a[maxn];
bool check(double x)//x是绳子的长度
{
int sum=0;//切分出来的绳子条数
for(int i=0;i<n;i++) sum=sum+a[i]/x;
if(sum>=k) return true;//切分出来的绳子条数大于等于k,说明选取的长度过小
else return false;//反之,选取的绳子长度过长
}
int main()
{
scanf("%d%d",&n,&k);//n条绳子,切出k条
for(int i=0;i<n;i++) scanf("%lf",&a[i]);
double l=0,r=100001,mid;
int cnt=0;
while(r-l>0.0001)//控制精度
{
mid=(l+r)/2;
if(check(mid)) l=mid;//切分的绳子太短,
else r=mid;
}
printf("%.2lf",l);
return 0;
}
例3:在线翻译
问题描述
你最近从滑铁卢搬到了另一个大城市,这里的人们说着一种难以理解的外语。幸运的是,你带着一本字典,它可以帮助你与这里的人们沟通。
输入数据
输入包含不多于100,000条词条,接着空一行,然后是待翻译的短文,包含不多于100,000个外语单词。每一个词条占一行,包含英语单词和对应的外语单词,两者之间用空格隔开。短文中每个外语单词占一行。输入保证没有重复的单词,且每个单词不多于10个小写字母组成。
输出要求
输出是翻译成英文的短文,每个英文单词占一行。如果有词典中没有出现的外语单词,则该单词应该被翻译成”eh”。
输入样例
dog ogday
cat atcay
pig igpay
froot ootfray
loops oopslay
改行为空行
atcay
ittenkay
oopslay
输出样例
cat
eh
loops
解题思路
1、由于词条量较大,可使用二分查找加快查找词条的速度。需要注意的是,本题不是查找数字,而是查找字符串,要用到字符串排序,因此比较简单的做法是建立字符串对的结构体,然后定义cmp()函数进行排序,字符串大小比较需要用到strcmp()函数。strcmp(a,b)返回小于0的值表示a<b,返回等于0的值表示a=b,返回大于0的值表示a>b。
2、本题还需要注意输入格式的问题,即如何准确发现空行。可以使用cin.peek()探查输入流中的下一个字符。如果下一个字符是换行符,则结束对词条的读入,转而进行翻译。同时注意每次使用cin.peek()前要使用cin.get()将每个词条的行末换行符处理掉。
3、实现技巧:
(1)输入输出较多,推荐使用scanf()和printf();
(2)自定义cmp()函数,对字符串使用sort()排序;
(3)灵活使用C和C++提供的输入函数(cin.get()、cin.peek()等)对输入进行处理。
参考代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct Entry
{
char english[11];
char foreign[11];
}entries[100005];
int Cmp(Entry e1,Entry e2)//自定义cmp()函数
{
return strcmp(e1.foreign,e2.foreign)<0;//如果e1<e2,则返回true
}
int main()
{
int num=0;
char word[11];
while(true)
{
scanf("%s%s",entries[num].english,entries[num].foreign);
num++;
cin.get();//去掉行末的换行符
if(cin.peek()=='\n') break;//查看是否是空行
}
sort(entries,entries+num,Cmp); //字符串从小到大排序
while(scanf("%s",word)!=EOF)
{
int left=0,right=num-1;
int n=0;
while(left<=right)
{
int mid=left+(right-left)/2;
n=strcmp(entries[mid].foreign,word);
if(n<0) left=mid+1;
else if(n>0) right=mid-1;
else
{
printf("%s\n",entries[mid].english);
break;
}
}
if(n) printf("eh\n");
}
return 0;
}
例4:农夫与牛
问题描述
农夫约翰建造了一座有n(2<=n<=100000)间牛舍的小屋,牛舍排在一条直线上,第i间牛舍在xi(0<=xi<=109)的位置,但是约翰的m(2<=m<=n)头牛对小屋很不满意,因此经常互相攻击,约翰为了防止牛之间互相伤害,因此决定把每头牛都放在离其它牛尽量远的牛舍。也就是要最大化最近的两头牛之间的距离。
牛并不喜欢这种布局,而且几头牛之间就放在一个隔间里,它们就要发生争斗。为了不让牛互相伤害,约翰决定自己给牛分配隔间,使任意两头牛之间的最小距离尽可能大,那么,这个最大的最小距离是多少呢?
输入格式
第一行是用空格分隔的两个整数n,m
第二行为n个用空格隔开的整数,表示位置xi输出格式
输出格式
输出仅一个整数,表示最大的最小距离值
样例输入
5 3
1 2 8 4 9
样例输出
3
样例解释
把牛放在第1,4, 8 间,这样最小的距离值是3
解题思路
1、这里可以假设一个函数check(d)表示可以安排牛的位置,并使得最近的两头牛的距离不小于d。
2、那么问题就转化为求满足check(d)的最大的d,另外,最近的间距不小于d也可以看成是所有牛的间距都不小于d,因此就可以用check(d)表示可以安排牛的位置,并使得最近的两头牛的间距不小d对这个问题,使用贪心可以容易求解:
1)对牛舍的位置x进行排序
2)把第一头牛放入牛舍x0
3)如果第i头牛放入了xj间牛舍,则第i+1头牛就要放入满足xj+d<=xk的最小牛舍xk中。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int a[maxn],n,m;//n是牛舍,m是牛的数量
bool check(int x)//任意两头牛的距离 ≥x
{
int sum=a[0],cnt=1; //sum=a[0]表示把第一头牛放进0号牛舍
for(int i=1;i<n;i++)
{
if(a[i]-sum>=x)//任意两头牛的距离 ≥x
{
cnt++;
sum=a[i];
}
}
if(cnt>=m) return true;//距离x可以放得下所有的牛
else return false;//距离x放不下所有的牛
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&a[i]);//读入牛舍的位置
sort(a,a+n);//牛舍排队
int l=0,r=a[n-1],mid;
while(l<r)
{
mid=(l+r+1)/2;
if(check(mid)) l=mid;//如果能放得下所有的牛,说明距离太小
else r=mid-1;//放不下所有的牛,说明距离太大
}
printf("%d",l);
return 0;
}
作业
习题1:查找最接近的元素
问题描述
在一个非降序中,查找与给定值最接近的元素。
输入
第一行包含一个整数n,为非降序列长度。1<=n<=100000。
第二行包含n个整数,为非降序列各元素。所有元素的大小均在0-1,000,000,000之间。
第三行包含一个整数m,为要询问的给定值个数。1<=m<=10000。
接下来m行,每行一个整数,为要询问的最接近元素的给定值。所有给定值的大小均在0-1000000000之间。
输出
输出为m行,每行一个整数,为最接近相应给定值的元素值,保持输入顺序。若有多个值满足条件,输出最小的一个。
输入样例
3
2 5 8
2
10
5
输出样例
8
5
参考代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int a[maxn],n,m;
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];//读入序列
cin>>m;
int t;
for(int i=0;i<m;i++)
{
cin>>t;//读入待查的数
int k=lower_bound(a,a+n,t)-a;//找到第一个≥t的位置
if(k>=n) cout<<a[n-1]<<endl;//越界,则是最后一个
else if(a[k]==t) cout<<a[k]<<endl;//正好匹配
else
{
if(n==1 || k==0) cout<<a[0]<<endl;//只有一个数,或者第一个数就比待查的大
else
{
if(t-a[k-1]>a[k]-t) cout<<a[k]<<endl;//比较左右两个数
else cout<<a[k-1]<<endl;
}
}
}
return 0;
}
习题2:月度开销
问题描述
农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。约翰打算为连续的M (1 ≤ M ≤ N) 个财政周期创建预算案,他把一个财政周期命名为fajo月。每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽可能少。
输入
第一行包含两个整数N,M,用单个空格隔开。接下来N列,每行包含一个1到10000之间的整数,按顺序给出接下来N天里每天的开销。
输出
一个整数,即最大月度开销的最小值。
样例输入
7 5
100 400 300 100 500 101 400
样例输出
500
最大值最小化
参考程序(需要回去琢磨一下)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=1e5+20;
int n,m,a[maxn];
bool check(int x)
{
int sum=a[0],cnt=1;
for(int i=1;i<n;i++)
{
sum+=a[i];//开销求和
if(sum<=x) continue;
else
{
sum=a[i];
if(a[i]>x) return false;
cnt++;
}
}
return cnt<=m;
}
int main()
{
scanf("%d%d",&n,&m);//n天,m个财政周期
int l=0,r=0;
for(int i=0;i<n;i++)
{
scanf("%d",a+i);//每天的开销
r+=a[i];//开销求和
l=max(l,a[i]);//
}
while(l<r)//最大值最小化
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
6.贪心算法
贪心算法是指从问题的初始状态出发,通过多次的贪心选择,最终得到整个问题的最优解。它是一种最接近人们日常思维的解题算法。由于贪心算法比较简单直观,因此在最优问题上有着广泛的应用。
贪心策略通常只考虑当前局部最优策略,最终得到全局的最优解。
在实际问题中,贪心策略通常会将问题切分成不同的阶段,并通过一系列的贪心选择来得到一个问题的最优解。
对于一个具体的贪心问题,在确定贪心选择之后,可以将问题转化为规模更小的子问题,同时也需要证明贪心策略的正确性,常用的方法包括数学归纳法等,通过对算法步骤或者问题规模的划分,叙述一个正确的命题,然后对于不同阶段进行归纳,证明每一步贪心选择的正确性,从而证明整个贪心策略的正确性。
例2
键盘输入一个高精度的正整数n(n<=2^40),去掉其中任意s个数字后剩下的数字按原左右次序将组成一个新的正整数。编程对给定的n和s,寻找一种方案,使得剩下的数字组成的新数最大。
算法思路
每一步总是选择一个使剩下的数最大的数字删去,即按高位到低位的顺序搜索,若各位数字递减,则删除最后一个数字;否则删除第一个递增区间的首字符。
参考代码
#include <bits/stdc++.h>
using namespace std;
string s;//存取大数
int n;//删除n位数
int main()
{
cin>>s>>n;
int cnt=0;
while(cnt!=n)
{
int len=s.size();
bool flag=false;//判断是否到最后一个字符 ,到达最后位置是false
for(int i=0;i<len;i++)
{
if(s[i]<s[i+1]) //前面的是高位,前面的小
{
cnt++;
flag=true;
s.erase(i,1);//删掉s[i]
break;
}
}
if(!flag)//到达最后的位置,说明一直递减
{
cnt++;
s.erase(len-1,1);//删掉最后一个
}
}
cout<<s;//输出新的字符串
return 0;
}
例3
某天KID利用飞行器飞到了一个金银岛上,上面有许多珍贵的金属,KID喜欢各种宝石的艺术品,可是也不拒绝这样珍贵的金属。但是他只带着一个口袋,口袋至多只能装重量为w的物品。岛上金属有s个种类, 每种金属重量不同,分别为n1,n2,…,ns,同时每个种类的金属总的价值也不同,分别为v1,v2,…,vs。KID想一次带走价值尽可能多的金属,问他最多能带走价值多少的金属。注意到金属是可以被任意分割的,并且金属的价值和其重量成正比。
算法分析:
有以下几种策略可供选择:
(1)每次挑选价值最大的物品装入背包,得到的结果是否最优?
(2)每次挑选所占重量最小的物品装入是否能得到最优解?
(3)每次选取单位重量价值最大的物品。
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+20;
struct node
{
double w,v;
double t;
bool operator<(const node &a)const
{
return t>a.t;//按照性价比由高到低排列
}
}p[maxn];
double tot;
int n;
int main()
{
scanf("%d%lf",&n,&tot);
for(int i=0;i<n;i++)
{
scanf("%lf%lf",&p[i].v,&p[i].w);
p[i].t=p[i].v/p[i].w;
}
sort(p,p+n);
double ans=0;
for(int i=0;i<n;i++)
{
if(tot>=p[i].w) ans+=p[i].v,tot-=p[i].w;
else
{
ans+=tot*p[i].v/p[i].w;
break;
}
}
printf("%.1lf",ans);
return 0;
}
例4 合并果子
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n-1次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 3种果子,数目依次为 1,2,9。可以先将 1、2堆合并,新堆数目为 3,耗费体力为 3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12,耗费体力为 12。所以多多总共耗费体力 =3+12=15 。可以证明15为最小的体力耗费值。
输入格式
第一行是一个整数 n(1≤n≤10000) ,表示果子的种类数。
第二行包含n个整数,用空格分隔,第i个整数 ai(1<=ai<=20000)是第i种果子的数目。
输出格式
一个整数,也就是最小的体力耗费值。输入数据保证这个值小于 2^31
输入样例
3
1 2 9
输出样例
15
【算法分析】
贪心策略:每次选取堆里面最小的两堆进行合并,然后把合并完成后的一堆再放回到原堆里面,继续选取两堆最小的进行合并,重复上述操作,直到最后所有果子合并为一堆,在合并的过程中累加答案即可。
参考代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
priority_queue<ll,vector<ll>,greater<ll> > que;//优先队列 :升序排列,小顶堆,每次弹出最小值
ll ans=0;
int n;
int main()
{
ios::sync_with_stdio(false);
cin>>n;//n 表示种类数
ll k;
for(int i=0;i<n;i++)
{
cin>>k;//第i种果子的数目
que.push(k);//入队
}
while(que.size()>1)
{
ll a=que.top();//取出最小值
que.pop();
ll b=que.top();//取出次小值
que.pop();
que.push(a+b);//求和并放入
ans+=a+b;
}
cout<<ans;
return 0;
}
优先队列补充
1 //升序队列,小顶堆,每次弹出最小值
2 priority_queue <int,vector<int>,greater<int> > q;
3 //降序队列,大顶堆,每次弹出最大值
4 priority_queue <int,vector<int>,less<int> >q;
作业
问题描述
给定一个十进制正整数n(0 < n < 10^10000),每个数位上数字均不为0。n的位数为m。
现在从m位中删除k位(0<k < m),求生成的新整数最小为多少?
例如: n = 9128456, k = 2, 则生成的新整数最小为12456
输入
输入一行,每一行包含两个数字n, k
输出
输出一个数字,表示从n中删除k位后得到的最小整数
样例输入
9128456 2
样例输出
12456
参考代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
string s;
int k;
cin>>s>>k;
string::iterator it;//迭代器
it=s.begin();
while(it<s.end())
{
if(*it>*(it+1))//前面的比后面的大
{
s.erase(it);//删掉
it=s.begin();//迭代器回到初始位置
k--;
if(k==0) break;
}
else it++;//否则迭代器后移
}
int len=s.size();
for(int i=0;i<len-k;i++) cout<<s[i];
return 0;
}
题目背景
战争已经进入到紧要时间。你是运输小队长,正在率领运输部队向前线运送物资。运输任务像做题一样的无聊。你希望找些刺激,于是命令你的士兵们到前方的一座独木桥上欣赏风景,而你留在桥下欣赏士兵们。士兵们十分愤怒,因为这座独木桥十分狭窄,只能容纳一个人通过。假如有两个人相向而行在桥上相遇,那么他们两个人将无妨绕过对方,只能有一个人回头下桥,让另一个人先通过。但是,可以有多个人同时呆在同一个位置。
题目描述
突然,你收到从指挥部发来的信息,敌军的轰炸机正朝着你所在的独木桥飞来!为了安全,你的部队必须撤下独木桥。独木桥的长度为L,士兵们只能呆在坐标为整数的地方。所有士兵的速度都为1,但一个士兵某一时刻来到了坐标为0或L+1的位置,他就离开了独木桥。
每个士兵都有一个初始面对的方向,他们会以匀速朝着这个方向行走,中途不会自己改变方向。但是,如果两个士兵面对面相遇,他们无法彼此通过对方,于是就分别转身,继续行走。转身不需要任何的时间。
由于先前的愤怒,你已不能控制你的士兵。甚至,你连每个士兵初始面对的方向都不知道。因此,你想要知道你的部队最少需要多少时间就可能全部撤离独木桥。另外,总部也在安排阻拦敌人的进攻,因此你还需要知道你的部队最多需要多少时间才能全部撤离独木桥。
输入格式:
第一行:一个整数L,表示独木桥的长度。桥上的坐标为1…L
第二行:一个整数N,表示初始时留在桥上的士兵数目
第三行:有N个整数,分别表示每个士兵的初始坐标。
输出格式:
只有一行,输出两个整数,分别表示部队撤离独木桥的最小时间和最大时间。两个整数由一个空格符分开。
输入样例#1:
4
2
1 3
输出样例#1:
2 4
说明:(初始时,没有两个士兵同在一个坐标)数据范围:N<=L<=5000。
参考代码
#include <iostream>
#include <algorithm>
using namespace std;
int a[5000+6];
int main()
{
int L,N;
cin>>L>>N;//读取桥的长度,和士兵的个数
int tmin=0,tmax=0,temp1,temp2;
for(int i=1;i<=N;i++) //遍历士兵
{
cin>>a[i];//记录桥上的士兵坐标
temp1=min(a[i],L+1-a[i]);//士兵距离下桥最小值
temp2=max(a[i],L+1-a[i]);//士兵距离下桥最大值
tmin=max(tmin,temp1);
tmax=max(tmax,temp2);
}
cout<<tmin<<' '<<tmax;
return 0;
}
7.动态规划
请参考笔者另一篇文章:动态规划(DP)
划分
问题描述
将正整数n 表示成一系列正整数之和,n=n1+n2+…+nk, 其中n1>=n2>=…>=nk>=1 ,k>=1 。
正整数n 的这种表示称为正整数n 的划分。正整数n 的不同的划分个数称为正整数n 的划分数。
输入
标准的输入包含若干组测试数据。每组测试数据是一个整数N(0 < N <= 50)。
输出
对于每组测试数据,输出N的划分数。
样例输入
5
样例输出
7
提示
5, 4+1, 3+2, 3+1+1, 2+2+1, 2+1+1+1, 1+1+1+1+1
#include <bits/stdc++.h>
using namespace std;
const int maxn=200;
int dp[maxn][maxn],n;
int main()
{
cin>>n;
//dp[i][j]表示i分成最大数为j的分法数
for(int i=1;i<=n;i++) dp[i][1]=dp[1][i]=dp[0][i]=1;
for(int i=2;i<=n;i++)
for(int j=2;j<=n;j++)
{
if(i>=j) dp[i][j]=dp[i-j][j]+dp[i][j-1];
else dp[i][j]=dp[i][i];
}
cout<<dp[n][n];
return 0;
}
8.深度优先搜索
9.广度优先搜索
马的遍历
题目描述
有一个nm的棋盘(1<n,m<=400),在某个点上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步
输入格式
一行四个数据,棋盘的大小和马的坐标
输出格式
一个nm的矩阵,代表马到达某个点最少要走几步(左对齐,宽5格,不能到达则输出-1)
输入 1
3 3 1 1
输出 1
0 3 2
3 -1 1
2 1 4
#include <bits/stdc++.h>
using namespace std;
const int maxn=500;
int n,m,sx,sy,len[maxn][maxn];
int dis[8][2]={-1,-2,1,-2,-2,-1,2,-1,-2,1,2,1,-1,2,1,2};//马的运动方向
queue<pair<int,int> > q;
bool vis[maxn][maxn];
void bfs()
{
q.push(make_pair(sx,sy));//入队
vis[sx][sy]=1;//标志
while(!q.empty())//队列不空
{
int x=q.front().first;
int y=q.front().second;
q.pop();//出队
for(int i=0;i<8;i++)
{
int dx=x+dis[i][0];
int dy=y+dis[i][1];
if(dx>=1 && dx<=n && dy>=1 && dy<=m && !vis[dx][dy])
{
vis[dx][dy]=1;
len[dx][dy]=len[x][y]+1;
q.push(make_pair(dx,dy));//入队
}
}
}
}
int main()
{
memset(len,-1,sizeof(len));//初始化为-1
scanf("%d%d%d%d",&n,&m,&sx,&sy);
len[sx][sy]=0;
bfs();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++) printf("%-5d",len[i][j]);
printf("\n");
}
return 0;
}
题目描述
给定一个n,k(0 <=n,k <= 100,000);对n有三种操作,分别为n=n+1,n=n-1,n=2*n。现在要求用最少的操作次数使得n变为k。
样例输入
5 17
样例输出
4
Hint
5-10-9-18-17
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int maxn=1e5+10;
int n,k,len[maxn];
bool vis[maxn];
void bfs(int n)
{
queue<int> q;
q.push(n);
vis[n]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
//n-1
int dx1=x-1;
if(dx1>=0 && dx1<=maxn && !vis[dx1])
{
vis[dx1]=1;
len[dx1]=len[x]+1;
if(dx1==k) return;
q.push(dx1);
}
//n+1
int dx2=x+1;
if(dx2>=0 && dx2<=maxn && !vis[dx2])
{
vis[dx2]=1;
len[dx2]=len[x]+1;
if(dx2==k) return;
q.push(dx2);
}
//n*2
int dx3=x*2;
if(dx3>=0 && dx3<=maxn && !vis[dx3])
{
vis[dx3]=1;
len[dx3]=len[x]+1;
if(dx3==k) return;
q.push(dx3);
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>k;
if(n>=k) cout<<n-k;
else
{
bfs(n);
cout<<len[k];
}
return 0;
}
题目描述
给出一张地图,这张地图被分为n×m(n,m<=100)个方块,任何一个方块不是平地就是高山。平地可以通过,高山则不能。现在你处在地图的(x1,y1)这块平地,问:你至少需要拐几个弯才能到达目的地(x2,y2)?你只能沿着水平和垂直方向的平地上行进,拐弯次数就等于行进方向的改变(从水平到垂直或从垂直到水平)的次数。例如:如图,最少的拐弯次数为5。
#include <iostream>
#include <queue>
using namespace std;
const int maxn=200;
int n,m,sx,sy,ex,ey,mp[maxn][maxn],len[maxn][maxn],dis[4][2]={-1,0,1,0,0,-1,0,1};
bool vis[maxn][maxn];
void bfs(int x,int y)
{
queue<pair<int,int> > q;
q.push(make_pair(x,y));
while(!q.empty())
{
int tx=q.front().first;
int ty=q.front().second;
q.pop();
for(int i=0;i<4;i++)
{
int dx=tx+dis[i][0];
int dy=ty+dis[i][1];
//找准一个方向一直走下去,直到走不通为止
while(dx>=1 && dx<=n && dy>=1 && dy<=m && !mp[dx][dy])
{
if(!vis[dx][dy])
{
//找到出口,且和队头在一个方向上,无需转弯
if(dx==ex && dy==ey)
{
cout<<len[tx][ty];
return;
}
vis[dx][dy]=1;//标记走过
len[dx][dy]=len[tx][ty]+1;
//继续往下走
q.push(make_pair(dx,dy));
}
dx=dx+dis[i][0];
dy=dy+dis[i][1];
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) cin>>mp[i][j];
cin>>sx>>sy>>ex>>ey;
bfs(sx,sy);
return 0;
}