蒟蒻的WA之路——二分法学习


前言

第一次写博客,版面可能有些惨不忍睹,不要在意这些细节,计划了一下,首先拿二分开刀吧,虽然原来学得基本忘了,差不多是从零开始重新学习,在这里浅薄地说一下我的理解。
在这里插入图片描述


一、二分是什么?

二分查找(英语: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;
}

五、结语

道阻且长,仍需努力。
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值