二分查找解题总结
https://vjudge.net/contest/439261
本周做题时存在的问题对题意的理解不足,题后分析不精。接下来是根据本周的关于二分查找的解题分析。
U题
打印页数分阶段,每段有固定费用.
对于每个pi输出相应的最小费用.
首先理解题意后还是没有思路,看了一些博主对二分查找的整理,理清楚了二分查找基本的思想,这道题的解题思路也属于其中。做了这么多题了基本都是这样的思路,如何解这道题呢,该题存在一个关键点是对应每个pi可能在该区间最优也可能在之后的区间最优,那么问题来了要找到它到底在哪个区间。这里使用二分法就可以找到大于等于pi的张数。最优,要么是pi×本区间费用,要么是最优区间左端点×对应区间的费用。解题时直接用到了一个二分的函数lower_bound(),直接找的第一个大于等于目标元素的位置。即能找到并转换知道pi所在的区间费用。之后顺便又复习了二分的这两个函数lower_bound( )和upper_bound()(lower_bound( )和upper_bound()的用法)。之前还特别诧异这两个函数怎么老出现在二分题的解题中,当时还特意不看那些题解,后来明白有些题目原来能简便不少。当结合这两个函数去理解二分查找时,还会进一步了解二分查找的功能,就很棒!
I题
这是一个好理解不好实践的题目,其他题都是不好理解不好实践。
最后能整出有中间变量r的两个方程,要找h我们会先求r和s,直接用s求r用到三角函数。转换思维,假设h存在,求出r,代入(2)式右边,明确和s的大小关系。思路就很清晰了
因此用二分去找合适的h,边界也很重要,h的取值。题干有说不能延展超过一半的L,那就好办了,0到1/2L。
数据是double类型时还要注意精度呀,不然无限循环还找不到哪里出错!
G题
//确定边界,初始边界未搞清楚
//此题是不能排序的,不能想当然进行排序
重新想这道题,不能排序是应该的,当时可能没理解题意,边界的话也好找,最小值到全部的和这一范围,先找mid对应的能分为多少区间,如果找到的区间数k大的话我们就扩大mid使一个区间的值大一点,这样我们能找到正好等于k的mid,但是是让求最大呀,但也不用担心,因为你找到的mid就是最优的啦。
还要特别注意代码怎么写,这里(如果找到的区间数k大的话我们就……)说得轻巧,周六做题就不会计数(头秃hlj),重置起点个数加一。
接下来两个题是当时最头疼的两个题
V题
一道最后提交上当时也很不理解,原因题意都不能自己复述就感觉可以做了(像平时做题不涉及算法的题目时也很容易看完样例就去解题,但其实没有充分的思考今天做题被自己的蠢无情地教育了一番严重意识到自己就在一个悬崖的边缘上不说了)回归正题二分算法,我们能清楚的知道最大值和最小值都是在变化着,但当写出几组样例分析后你就会清楚地知道一组数的最大值和最小值分别是有下限和上限的。
我们找上限下限有什么用呢?那就从题干的问题出发,求K天后最大值和最小值的差。自己写几组样例发现最后能中和,中和又分为两种情况,那就看sum/n了。询问k天后的最大和最小,所以为了避免最大和最小取到不存在的值,边界的设定很重要。我们利用二分去找最大和最小就需要找到各自的边界。但我不太理解为什么要用二分查找,其他方法不行吗?我又去翻了原题别人的代码,发现用二分查找很合理,就突然又意识到二分很巧妙。
//找最小
int check(ll x)
{//找最小
ll res = 0;//记录天数
for(int i = 1; i <= n; i++)
{
if(a[i]<x) res+=x-a[i];
}
return res<=k;
}
………………………………
while(l <= r)
{
mid = (l+r)/2;
if(check(mid))
{
ansx = mid;
l = mid + 1;
}
else
r = mid - 1;
}
//找最大
int check2(ll x)
{//找最大
ll res = 0;
for(int i=1; i<=n; i++)
{
if(a[i]>x) res+=a[i]-x;
}
return res<=k;
}
……………………………………
while(l <= r)
{
mid=(l+r)/2;
if(check2(mid))
{
ansy = mid;
r = mid - 1;
}
else
l = mid + 1;
}
Q题
洗衣机甩干衣服的题。
ceil()函数,很多人说它不好使,就是用了(x+k-2)/(k-1)
的方式向上取整。但我又查了一下ceil()函数之所以不好使是因为它适用于dounle类型,像ceil(28.0/7.0)时,可以整除那么函数体内变为了整型取整就会失效。那将它改为ceil(1.0*(28.0/7.0))即可AC。这个地方让我当时很晕,也不清楚当时为什么想不明白。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cmath>
using namespace std;
typedef long long ll;
ll a[100010];
ll k;
int n;
//我们可以先假设m的存在,
//k要减去一是因为本身就会消失一份子的水
bool check(ll m)
{
ll x=0;
for(int i=0; i<n; i++)
{
if(a[i]>m)
// x+=(a[i]-m+k-2)/(k-1);//新方法,比ceil快
x+=ceil(1.0*((a[i]-m)*1.0/(k-1)));
//cout<<x<<endl;
if(x>m) return 0;
}
return 1;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
for(int i=0; i<n; i++)
scanf("%lld",&a[i]);
sort(a,a+n);
scanf("%lld",&k);
if(k==1)
{
printf("%lld\n",a[n-1]);
continue;
}
ll l,r;
l=1;
r=a[n-1];
while(l<r)
{
ll mid=l+r>>1;
if(check(mid))
r=mid;
else l=mid+1;
//cout <<l<< " "<<r << endl;
}
printf("%lld\n",l);
}
return 0;
}
(把经历超曲折的代码附上)
这周对于二分的学习,我觉得做题收获不多但总结确实让我对二分进一步的理解,也可以说对二分的理解都是靠总结得来的。
这一周还学了搜索,准确说是知道了搜索,好难题干我都有点理解不了,周六别人做了几小时的题我补了接近一天,晚上看了搜索的知识不懂,头发掉了不少。
————————————————————
做题想不到点子上,气死我了
https://vjudge.net/contest/441257
A题
n个问题要解决,每个问题花费i*5分钟,路途花费k分钟,一共240分钟,问最多能解多少。
k的初值设为240-k
找一变量记录解题数num
循环减去做题时间直到k小于等于0
我想的是
k大于235的情况
k小于235的情况,有多少个5,找到能解的题的个数,如果提前到达了n就输出n,简单的说我循环的是剩余时间找剩余时间能解题的个数,本来就复杂啦结果关系还找错。
B题
n个房间,总体累计标号,每个房间单独标号是从1开始。输入k个数,找到它在哪个房间哪个位置输出。
while(k–)
{
cin >> x;
while(x>a[j]) j++;
// if(x<=a[j])
cout<<j<<" "<<x-a[j-1]<<endl;
}
我的,
while(k–)
{
cin >> x;
if(x<=a[j]&&x>a[j-1])
cout<<j<<" “<<x-a[j-1]<<endl;
else if(x>a[j])
{
j++;
//if(x<=a[j]&&x>a[j-1])
cout<<j<<” "<<x-a[j-1]<<endl;
}
}
少了一种情况,当这个数大到垮了几个房间的时候我的代码就不行啦。
C题
由0和1组成的n个数,只能走1,相隔不能超过d的1一步到达。问是否能到达终点,需要几步走。
小伙伴儿跟我说逆序我昨天还想不明白,今天就懂了。先来正序的,我写的代码答案是对的但复杂度高超时啦
#include
#include
#include
using namespace std;
typedef long long ll;
string a;
int b[100005];
int main()
{
int n,d,num=0,j=0;
cin>>n>>d;
for(int i=0;i<n;i++)
{
cin >> a[i];
if(a[i]== ‘1’)
{
b[j]=i;
j++;
}
}
int x=b[0],f=1;
for(int i=f; i<j;)
{
while(b[i]-x<d) i++;
if(b[i]-x== d)
{
num++;
x=b[i];
i++;
}
if(b[i]-x>d)
{
if(i==1)
{
num=-1;
break;
}
num++;
x=b[i-1];
}
f=i;
}
cout << num << endl;
return 0;
}
正序新方法:
#include
#include
using namespace std;
typedef long long ll;
string a;
int b[100005];
int main()
{
int n,d,num=0,j=0;
cin>>n>>d;
cin>>a;
int x=0,y=0;
while(x<n-1&&y<=n)
{
if(a[x]==‘1’)
{
x+=d;
y++;
}else x–;
}
if(y>n)
cout<<-1<<endl;
else
cout<<y<<endl;
return 0;
}
再来逆序:
直接找就好因为到达最后一个位置,最宽松的条件就是,最后一步是跨越d距离到达的,那么直接找之前是否有超越d的找到就输出-1。如果没有直接输出步数。小伙伴的代码留下来
while(1)
{
x=i+d;
if(x>=n) break;
while(a[x]!=‘1’)
{
x–;
if(x==i) {cout<<-1<<endl;return 0;}
}
i=x;
sum++;
}
cout<<sum<<endl;
D题代码子优化一下
代码子:
#include
using namespace std;
typedef long long ll;
ll n;
int main()
{
cin>>n;
ll num=0;
while(n!=-1)
{
if(n>=100)
{
num+=n/100;
n=n%100;
}
else if(n>=20)
{
num+=n/20;
n=n%20;
}
else if(n>=10)
{
num+=n/10;
n=n%10;
}
else if(n>=5)
{
num+=n/5;
n=n%5;
}
else if(n<5)
{
num+=n;
n=-1;
}
}
cout<<num<<endl;
return 0;
}
代码子:
int main()
{
int i, j, n, b, s, m, t, l, r, p, q;
scanf("%d", &n);
b = n / 100;
p = n % 100;
s = p / 20;
q = p % 20;
t = q / 10;
r = q % 10;
m = r / 5;
l = r % 5;
printf("%d\n", (b + s + m + t + l));
return 0;
}
★E题
Input
7
2 2 2 1 1 2 2
Output
4
Input
6
1 2 1 2 1 2
Output
2
Input
9
2 2 1 1 1 2 2 2 2
Output
6
找到连续两者最少的1或2的个数(表达不当就这个意思)
错在,把每个个位置都赋值了个数,然而不是连续数的最终个数,所以两个连续数之间最小值不好找了,所以没必要。就设另一个j作为下标,如果是连续数子直接加一就可,不必改变下标,这样每组连续数字的个数就好确定啦,就很棒。再来个循环就找到啦
★for(int i=1; i<=j; i++)
ans=max(ans,min(b[i],b[i-1]));