这周的题目比上周更难了,但是在做题过程中也学到了很多新知识。首当其次便是二分法。
看到这里你可能会感到很疑惑,“什么?二分不是初学者都会写吗”。确实,写二分非常简单,但这几道题难就难在你不知道要用二分,不知道怎么用二分。
首先来看第一题,这也是最简单的一题
“本题输入输出量较大,请使用较快的 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;
}
本周的学习就到此结束了。很抱歉,由于这周(其实是已经上周了)学校的事情太多,作者没能实现一周一更的承诺,这篇博客写的也挺匆忙。这周一定按时发布课业博客,望读者大大海涵。