week 2 二分主场

这周的题目比上周更难了,但是在做题过程中也学到了很多新知识。首当其次便是二分法。

看到这里你可能会感到很疑惑,“什么?二分不是初学者都会写吗”。确实,写二分非常简单,但这几道题难就难在你不知道要用二分,不知道怎么用二分。

首先来看第一题,这也是最简单的一题

“本题输入输出量较大,请使用较快的 IO 方式。”这句话直接掐断多少人暴力循环查找的念头。显然,二分查找对这题来说是非常合适的。但是,我又在解决这道题的时候学习到了一个新知识:stl里有自带的二分函数:upper_bound 和 lower_bound 。前者的意思是返回第一个大于搜索数的位置,后者的意思是返回第一个大于等于的数的位置。在这道题中,显然后者更好用一点。

注意!!!返回的是位置!所以肯定要用到数组,并且返回后还得减去数组本身(位置)

下面是题解

#include <bits/stdc++.h>
using namespace std;
int A[1000010],B[1000010];
int main(){
   int n,m;
   cin>>n>>m;
   for(int i=1;i<=n;i++){//注意注意注意,从int从1开始!
   	cin>>A[i];
   }
   for(int i1=0;i1<m;i1++){
   	cin>>B[i1];
   }
  for(int k=0;k<m;k++){
  	int x=lower_bound(A+1,A+n+1,B[k])-A;//注意注意注意,新知识
  	if(B[k]!=A[x]){
  		cout<<"-1";
	  }
	if(B[k]==A[x]){
		cout<<x;
	}
	if(k!=m-1){
		cout<<' ';
	}
  }
	return 0;
}

由于思路简单易懂,注释就不多写了。这里再附上手打二分解法,选用手打二分解法其实对后面的解题更有帮助。

#include <bits/stdc++.h>
using namespace std;
int n,m,q,a[1000005];
int find(int x) //二分查找 
{
	int l=1,r=n;
	while (l<r)
	{
		int mid=l+(r-l)/2;
		if (a[mid]>=x) r=mid;
		else l=mid+1;
	}
	
	if (a[l]==x)
	 return l; //找对了
	else 
	 return -1; //没找对
}

int main()
{
	cin>>m>>n; 
	for (int i=1 ; i<=n ; i++)
	    cin>>a[i]; 	
	for (int i=1 ; i<=m ; i++)
	{
		cin>>q;
		int ans=find(q); //看看查找的结果 
		cout<<ans; 
	}
	return 0;
}

接下来就是第二题了,先看题

这道题说实话,我一开始就没想到用二分,甚至在知道要用二分解的时候还疑惑了很久。最终本蒟蒻在反复观摩来自洛谷的Accele_Rator大佬的详细解读后才明白这题要怎么使用二分。

对大佬的代码做了一些修改,现在可能更好读一点

#include <bits/stdc++.h>
using namespace std;
int A[1000010];
int n,c,a;
bool check(int x) //bool判断(本题最核心思路)
{
    int num=0;
    int l=A[1]; 
    for(int i=2;i<=n;i++)
    {
        if(A[i]-l<x) 
		num++; //若此距离不满足当前答案,那么需要的牛栏数+1,即把当前牛放到下一个牛栏 
        else 
		l=A[i]; //否则就更新上一次的牛栏位置 ,即上一头牛放的位置 
        if(num>a) 
		return false; //若需要牛栏数大于最大牛栏数,此答案不可行 
    }
    return true; //反之,若需要牛栏数小于最大需要牛栏数,此答案可行
}
int main()
{ 
  cin>>n>>c;
  for(int i=1;i<=n;i++){
  	cin>>A[i] ;
  }
  sort(A+1,A+n+1); //排序
  a=n-c; //最大剩余牛栏数 
  int l=1; //一定为最小
  int r=A[n]-A[1]; //一定为最大 
  while(l+1<r) //若左<右,则继续二分答案 
  {
      int mid=(l+r)/2; //二分两区间分别为l ~ mid,mid ~ r;
      if(check(mid))
	  l=mid; //若此答案可行,从mid ~ r区间继续查找(更大答案),即修改左界l=mid 
      else 
	  r=mid; //反之,若此答案不可行,从l ~ mid区间查找(合理答案),即修改左界l=mid 
  }
  if(check(r)) 
  cout<<r ;//若可行解为右界,输出右界 
  else 
  cout<<l ;//若可行解为左界,输出左界 
  return 0;
}

可以发现,这道题的核心判断点就在于二分的结果与牛栏跨度大小的比较。思路已经很难想了,能够写出来更不简单,笔者在这里再次对大佬表示膜拜。

然后便是最后一题了,先看题

 

这道题在第二题难度的基础上更进一步。我也通过这道题学习了两个新知识:位运算加速以及贪心算法。这题用到位运算加速中的>>n,其含义为“>>”左边的数的二进制形式往右移动n位。比如4>>1,意思就是3的二进制形式0100右移一位变成0010,运算结果为2。而贪心算法由于内容比较多,其中更有许多我不甚了解的细枝末节,我就不详细赘述了。想对贪心算法进行了解的读者点击下面链接即可。这道题采用的便是贪心算法的局部最优化的想法。

(14条消息) 贪心算法(贪婪算法)_一个软泥怪的博客-CSDN博客_贪心算法

题解:

#include <bits/stdc++.h>
using namespace std;
int sto[100000];//开大一点,保险
int main()
{
    int s,n,m;
    cin>>s>>n>>m;
    int zuo=1,you=s,mid;//所有边界为1、s
    for(int i=0;i<n;i++)
    cin>>sto[i];
    sort(sto,sto+n);//从小到大排序
    int sg,cnt,ii;
    while(zuo!=you)
    {
        mid=(zuo+you+1)>>1;//位运算加速
        sg=cnt=0;//初始化
        for(ii=0;ii<n;ii++)
        {
            if(s-sto[ii]<mid)
			break; //如解析中所述,若再跳x已超过终点,则不可取此点,它后面的也显然不可取
            if(sto[ii]-sg<mid)
			cnt++; //跳过
            else 
			sg=sto[ii]; //贪心,直接跳到
        }
        cnt+=n-ii;//统计最后被删除的点数
        if(cnt<=m)
		zuo=mid;
        else 
		you=mid-1;//二分边界更新,具体请见解析
    }
    cout<<zuo;
    return 0;
}

本周的学习就到此结束了。很抱歉,由于这周(其实是已经上周了)学校的事情太多,作者没能实现一周一更的承诺,这篇博客写的也挺匆忙。这周一定按时发布课业博客,望读者大大海涵。

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无用夜宵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值