首先说明,博主还只是一个小菜鸟,趁这个机会回顾一下最近学习的思想和算法,有任何错误以及改进之处,敬请指正:
- 二分法
- 三分法
- 尺取法
- 需要注意的是,简单查找必须要按照关键字信息先排序,我一般是用sort,也可以按照自己的个人习惯排序。
二分查找
折半查找法也称为二分查找法,它充分利用了元素间的次序关系,采用分治策略,可在最坏的情况下用O(log n)完成搜索任务。它的基本思想是,将n个元素分成个数大致相同的两半,取a[n/2]与欲查找的x作比较,如果x=a[n/2]则找到x,算法终止。如 果x小于a[n/2],则我们只要在数组a的左半部继续搜索x(这里假设数组元素呈升序排列)。如果x大于a[n/2],则我们只要在数组a的右半部继续搜索x。 —— [百度百科 ]
二分查找可以分为整型和浮点型,算法思路基本相同,每次将问题分为两部分,每一次循环就能去掉其中的一半,时间复杂度是O(logn)主要步骤:
1.判断上界和下界作为边界值
2.跳出循环的条件
3.相同值的处理
不多说了,先上例题
整型51Nod-1267 4个数的和
给出N个整数,你来判断一下是否能够选出4个数,他们的和为0,可以则输出”Yes”,否则输出”No”。
奉上链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=126
这道题与给出四个数组不同,它还要求每个元素最多只能使用一次,也就是说另外要记录已经使用过的元素,保证它不被二次使用,博主是用了一个结构体记录位置,听大佬说还可以使用map来着,但是博主比较菜,没听懂,囧!
代码
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
long long a[1005];
struct coor
{
int x;
int y;
long long sum;
}su[1000000]; //用结构体中的x,y记录组成sum的两个元素
int cmp(coor x,coor y);
int binary_search(int n)
{
long long right,left;int bug=0;
for(int k=0;k<n-1;k++)
for(int s=k+1;s<n;s++)
{
left=0;
right=n*(n-1)/2;
while(left+1<right)/*注意这里,跳出循环的条件,如果写成left<right,需要在内部进行更多判断*/
{
long long mid=(right+left)/2;
if((su[mid].x!=k) && (su[mid].y!=k) &&(su[mid].x!=s) &&(su[mid].y!=s))
{
if(su[mid].sum+a[k]+a[s]==0)
{
bug=1;
break;
}
else if(su[mid].sum>(-a[k]-a[s]))/*注意状态的变化,right和left的变化不要写反了,搞不清楚建议用笔演算一下*/
right=mid;
else
left=mid;
}
else
break;
}
}
if(bug==1) return true;
else return false;
}
using namespace std;
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%I64d",&a[i]);
int tot=0;
for(int i=0;i<n-1;i++)
{
for(int j=i+1;j<n;j++)
{
su[tot].sum=a[i]+a[j];
su[tot].x=i;
su[tot].y=j;
tot++;
}
}
sort(su,su+(n*(n-1)/2),cmp);
if(binary_search(n))
printf("Yes");
else
printf("No");
return 0;
}
int cmp(coor x,coor y)
{
return x.sum<y.sum;
}
浮点型 HDU-2289 CUP
题目大意:已知圆台本身的底面半径r和顶面半径R,以及本身的高H,现在往里面注入V体积的水,求水的高。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2289
这道题数学比较好的人可以直接根据相似几何体的关系,直接求高,博主已经亲身实验过了,可以,但是很烦(可能是博主的数学比较水),这里我建议是二分查找水的高
代码
#include<stdio.h>
#include<math.h>
#define PI 3.1415926535
double tmp,r;
double eps=0.00000001;
double cal(double n);
int main()
{
int T;
scanf("%d",&T);
for(int i=0;i<T;i++)
{
double R,h,V;
scanf("%lf%lf%lf%lf",&r,&R,&h,&V);
double left=0,right=h; /*敲黑板,上界下界一定要考虑清楚,博主就是因为把下界写成了圆台本身的高下界1,所以wa了好几发*/
tmp=(R-r)*1.0/h;
while(fabs(right-left)>eps)/*这是其中一种写法,也可以int time=100,while(time--)*/
{
double mid=(left+right)*1.0/2;
if(fabs(cal(mid)-V)<eps)
break;
if(cal(mid)<V)
left=mid;
else
right=mid;
}
printf("%.6lf\n",right);
}
return 0;
}
double cal(double n)//圆台的体积计算公式
{
double R_mid=n*tmp+r;
double V_mid=PI/3.0*(r*r+R_mid*R_mid+R_mid*r)*n;
return V_mid;
}
二分法基本就告一个段落了,库函数里的lower_bound和upper_bound,有兴趣的小伙伴也可以去学一学
三分法
遇到凸型或凹型函数时,可以用三分查找求那个凸点或者凹点,基本步骤和二分法其实没什么区别,我就不多说了。
如图,找到两个最边界(极限状态就是两边都是无穷大),找到mid1,据说黄金分割点是最佳状态,但是博主更喜欢直接取半,再将mid1和right相加取半,可以保证mid2在mid1和right之间,这样就不会错过那个最凹(凸)点。最后是要确定跳出循环的条件,一般循环个一百次或者保证左右相差不超过某个很小的值。
这里给出三分法的套路:
while(fabs(r-l)>0.0000001)/*int time=100,while(time--)*/
{
double mid1=(r+l)/2;
double mid2=(mid1+r)/2;
ans1=cal(mid1);
ans2=cal(mid2);
if(ans1<ans2)
l=mid1;
else
r=mid2;
}
例题
CodeForces-780B The Meeting Place Cannot Be Changed
题目大意:数轴上有N个点,第i个人站在Xi的位置,每个人以Vi的速度移动,求能够使所有人以最短时间汇合的地点坐标
题目链接:http://codeforces.com/problemset/problem/780/B
考虑极限状态,如果该地点在无穷小或者在无穷大,所用的时间应该是无穷大,那么可以充分相信,地点应该是在最右边和最左边的人之间,以此为边界进行计算。
这道题套公式就可以了,博主用了个结构体记录坐标和速度,以此求最短时间。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
#define exp 0.000001
using namespace std;
struct friends
{
int coor;
int speed;
}fri[60005];
int cmp(friends x,friends y);
double cal(double place);
double tmp;
int num;
int main()
{
double right=0,left=0,ans1,ans2;
scanf("%d",&num);
for(int i=0;i<num;i++)
scanf("%d",&fri[i].coor);
for(int i=0;i<num;i++)
scanf("%d",&fri[i].speed);
sort(fri,fri+num,cmp);
left=fri[0].coor*1.0;
right=(fri[num-1].coor)*1.0;
while(fabs(right-left)>exp)
{
double mid1=(left+right)/2.0;
double mid2=(mid1+right)/2.0;
ans1=cal(mid1);
ans2=cal(mid2);
if(ans1>ans2)
left=mid1;
else
right=mid2;
}
printf("%lf",cal(left));
return 0;
}
int cmp(friends x,friends y)
{
return (x.coor<y.coor);
}
double cal(double place)
{
tmp=0;
for(int i=0;i<num;i++)
{
double timed=fabs(fri[i].coor-place)*1.0/fri[i].speed;
tmp=max(tmp,fabs(fri[i].coor-place)*1.0/fri[i].speed);
}
return tmp;
}
尺取法
尺取法:顾名思义,像尺子一样取一段,尺取法通常是对数组保存一对下标(起点和终点),然后根据实际情况交替推进两个端点直到得出答案。尺取法比暴力枚举的效率要高很多,特别是在数据量比较大的情况更需要用尺取法。
综上,尺取法的要注意的以下几点:
1.什么时候能用尺取法?
(所选区间有一定规律,并且能根据这个规律推进左右端点)
2.什么时候推进端点?
(不断扩大右端点,直到满足条件,在满足条件后扩大左端点+1,继续扩大右端点,重复直到左右端点走到最后,选取最小区间)
这时候又是例题时间,直接通过例题感受一下尺取法的魅力吧。
51Nod-1127最短的包含字符串
题目:给出一个字符串,求该字符串的一个子串S,S包含A-Z中的全部字母,并且S是所有符合条件的子串中最短的,输出S的长度。如果给出的字符串中并不包括A-Z中的全部字母,则输出No Solution。
题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1127
另左右端点都在数组s[0]的位置,不断推进右端点,并且记录其中每一个字母出现的次数,直到26个字母都出现了,此时right不动,再看左端点,只要该字母出现的次数已经超过一了,就不断推进左端点,直到left对应的字母出现次数等于1,推进右端点,通过ans记录right-left的最小值
代码
#include<stdio.h>
#include<string.h>
char s[100005];
int num[26]={0};
int main()
{
scanf("%s",s);
int left=0,right=0,len,ans=100005,acount=0;
len=strlen(s);
while(left<=right && right<len)
{
if(num[s[right]-'A']==0)
acount++;
num[s[right]-'A']++;
while(num[s[left]-'A']>1)
{
num[s[left]-'A']--;
left++;
}
if(acount==26)
{
ans=ans>(right-left+1)?(right-left+1):ans;
}
right++;
}
if(ans!=100005)
printf("%d",ans);
else
printf("No Solution");
return 0;
}
好啦,今天就到这里啦!无论是二分、三分还是尺取,博主还是有很多要学习的地方,上文有任何不对的地方,希望大家指正啦~~