前言
第一次写博客,版面可能有些惨不忍睹,不要在意这些细节,计划了一下,首先拿二分开刀吧,虽然原来学得基本忘了,差不多是从零开始重新学习,在这里浅薄地说一下我的理解。
一、二分是什么?
二分查找(英语:binary search),也称折半搜索(英语:half-interval search)、对数搜索(英语:logarithmic search),是用来在一个有序数组中查找某一元素的算法。
二、二分的原理
以在一个升序数组中查找一个数为例。
它每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需到右侧查找;如果中间元素大于所查找的值同理,只需到左侧查找。
三、使用步骤
1.基本模板
这里放上一段基本是二分最基础范例模板,代码如下(示例):
int BinarySearch(int all[],int size,int goal)
{
int L=0;
int R=size-1;
while(L<=R)
{
int mid=L+(R-L)/2; //防止直接L+R过大
if(goal==a[mid])
return mid;
else if(goal>a[mid])
L=mid+1;
else
R=mid-1;
}
return -1;
}
2.应用
首先当然是查找数组中特定的数据了,这也是初学二分最基础的部分,这里不再赘述。
其次二分可以实现方程求解,虽然不是最优的方案,但是相较于暴力枚举来一个个代入实验自有其优越性。
再者就是解应用题了,这也是最难的部分。实际上做了一段时间的题就逐渐可以发现,很多的以二分为考点的题目初读起来根本看不出来这是二分的题目,只有不断抽丝剥茧才能发现其规律。
四、例题
先看第一道基础题。
思路一:暴力枚举
思路二:
1,将数据排序
2,对数组中的每个元素a[i],在数组中二分查找m-a[i],看能否找到。复杂度log(n),最坏要查找n-2次
思路三:
1,将数组排序,复杂度是O(n×log(n));
2,查找的时候,设置两个变量i和j,i初值是0,j初值是n-1.看a[i]+a[j],如果大于m,就让j 减1,如果小于m,就让i加1,直至a[i]+a[j]=m。
思路一和思路二是比较容易想到的,思路三我是真的没有想到,看了ppt想了很长时间才逐渐理解这种思路,要学的还有很多。
//这里放上我的代码,如有疏漏,敬请指正。
#include<iostream>
#include<algorithm>
using namespace std;
int a[100010];
int n, m;
void solve1()
{
int num1 = 0, num2 = 0;
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n && a[i] < m; i++)
{
int goal = m - a[i];
int l = 1, r = n;
bool flag = 0;
while (l <= r&&flag==0)
{
int mid = l + (r - l) / 2; //防止数据过大
if (a[mid] == goal)
{
num1 = a[i];
num2 = goal;
flag = 1;
}
else if (a[mid] > goal)
r = mid - 1;
else if (a[mid] < goal)
l = mid + 1;
}
if (flag == 1)
break;
}
cout << num1 << " " << num2 << endl;
}
void solve2()
{
int i = 1, j = n;
sort(a + 1, a + 1 + n);
while (1)
{
if (a[i] + a[j] > m)
j--;
else if (a[i] + a[j] < m)
i++;
else
break;
}
cout << a[i] << " " << a[j] << endl;
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
//暴力枚举不列出
cout << "solve1:" << endl;
solve1();
cout << "solve2:" << endl;
solve2();
}
再来看一道题目。
Given a N × N matrix A, whose element in the i-th row and j-th column Aij is an number that equals i2 + 100000 × i + j2 - 100000 × j + i × j, you are to find the M-th smallest element in the matrix.
翻译:给定一个N×N矩阵a,其第i行和第j列Aij中的元素是一个等于i2+100000×i+j2-100000×j+i×j的数,您将找到矩阵中第M个最小的元素。
我对这道题真的可以说是印象特别的深刻,第一遍做的时候完全没有感觉到它和二分有任何的关联,更多的感觉是跟方程求解有关。读完一遍开始分析,嗯,求导,分类讨论,代换,经过一系列花里胡哨的操作整好提交,结果当然是WA了。接着又想了很长时间无果,只好进行求助。当我了解到这个需要二分的时候真的非常意外,不禁感叹算法的巧妙。
解法:二分套二分。
我们首先二分第m大的数是多少,二分出来以后,我们去判断整个NN的矩阵里面小于它的数有多少个。通过观察ii + 100000 * i + j*j - 100000 * j + i * j 这个式子,我们可以发现,当j不变时,式子的值是随着i的变大而变大的,即随i单调递增。那么我们就枚举每一列,然后二分所有的行,看看在那一列中小于那个数的有多少,统计所有列的情况,加起来,判断小于的个数是否。
#include<iostream>
#include<stdio.h>
using namespace std;
long long n;
long long cal(long long i,long long j){
return i*i+100000*i+j*j-100000*j+i*j;
}
long long judge(long long d){
long long l,r,ans=0,sum=0,mid;
for(int i=1;i<=n;i++){
l=1;r=n;
while(l<=r){
mid=l+(r-l)/2;
if(cal(mid,i)<=d){
ans=mid;
l=mid+1;
}
else{
r=mid-1;
}
}
sum=sum+ans;
}
return sum;
}
int main(){
long long l,k,m,r,mid,ans;
cin>>k;
while(k--){
ans=0;
cin>>n>>m;
l=-1*0x3f3f3f3f3f3f3f,r=1*0x3f3f3f3f3f3f3f;
while(l<=r){
mid=(l+r)/2;
if(judge(mid)>=m){
ans=mid;
r=mid-1;
}
else
l=mid+1;
}
cout<<ans<<endl;
}
return 0;
}
五、结语
道阻且长,仍需努力。