蓝桥杯 第二讲 二分与前缀和
一、二分
1.整数二分
整数二分步骤:
- 找一个区间[L,R],使得答案一定在该区间中
- 找一个判断条件,使得该条件具有二段性,并且答案一定是该二段性的分界点
- 分析中点M在该判断条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间;
- 如果更新方式写的是R = Mid,,则不用做任何处理;若更新方式写的是L = Mid,则需要在计算Mid上加1
789. 数的范围
int find_l(int x)
{
int l = 0, r = n-1;
while(l < r)
{
int mid = l+r>>1;
if(x <= a[mid]) r = mid;
else l = mid + 1;
}
return l;
}
int find_r(int x)
{
int l = 0,r = n-1;
while(l < r)
{
int mid = l+r+1>>1;
if(x >= a[mid]) l = mid;
else r = mid - 1;
}
return l;
}
2.实数二分
实数较为稠密,对边界问题要求较低
790. 数的三次方根
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
double double_binary(double x)
{
double l = -10000,r = 10000;
while(r-l > 1e-8) //精度取较大些更保险
{
double mid = (l+r)/2;
if(mid*mid*mid <= x)
{
l = mid;
}
else if(mid*mid*mid > x)
{
r = mid;
}
}
return l;//精度差值符合范围,输出任意均可
}
int main()
{
double x;
cin>>x;
printf("%.6lf",double_binary(x));
return 0;
}
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,h[N],Maxh = -1;
bool check(int x)//x表示起始能量
{
for(int i=1;i<=n;i++)
{
x = 2*x - h[i]; //根据题意得出的能量递推公式
if(x<0) return false;//如果过程中出现能量小于0的情况,说明x小了
if(x>=Maxh) return true;//如果过程中出现能量大于最大高度的情况,根据公式,x是递增的,说明一定是正数
}
return true;//没有出现小于0的情况,符合题意的x
}
int binary_search()//二分找到能完成任务的最小的初始能量值,用从左边找的模板
{
int l = 0,r = 1e5;//r为最大能量
while(l<r)
{
int mid = l+r>>1;
if(check(mid)) r = mid;//如果mid成立,说明可能还有比mid小的可成立的数,范围向左缩小一半
else l = mid + 1;//不成立,范围向右缩一半
}
return l;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&h[i]);
Maxh = max(Maxh,h[i]);//找出最大高度
}
int res = binary_search();
cout<<res<<endl;
return 0;
}
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 1e7;
int n;
struct Sum
{
int s;
int c;
int d;
bool operator<(const struct Sum &t)const
{
if(s!=t.s) return s<t.s;
if(c!=t.c) return c<t.c;
return d<t.d;
}
}sum[N];
int main()
{
cin>>n;
int cnt = 0;
for(int c = 0;c*c <= n;c++)
{
for(int d = c;c*c + d*d <= n;d++)
{
sum[cnt++] = {c*c + d*d,c,d};
}
}
sort(sum,sum+cnt);//按照sum,c,d关键字升序排序
for(int a=0;a*a<=n;a++)//升序枚举a和b
{
for(int b=0;a*a+b*b<=n;b++)
{
int l=0,r = cnt-1;//从左边二分找到对应的平方和,使得c和d最小
while(l<r)
{
int mid = l+r>>1;
if(sum[mid].s >= n-a*a-b*b) r = mid;
else l = mid + 1;
}
if(sum[l].s == n-a*a-b*b)
{
printf("%d %d %d %d\n",a,b,sum[l].c,sum[l].d);
return 0;
}
}
}
return 0;
}
#include<iostream>
#include<cmath>
using namespace std;
const int N = 1e5+10;
int n,k;
int h[N],w[N];
bool check(int x)//x表示切除的正方形边长,x越大,分的个数越少,反之越大,
{
int sum = 0;
for(int i=0;i<n;i++)
{
sum += (h[i]/x)*(w[i]/x);//表示以边长x分得的个数,每个人边长都相等
if(sum >= k) return true;
}
return false;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
{
scanf("%d%d",&h[i],&w[i]);
}
int l=1,r = 1e5;//输入保证每位小朋友至少能获得一块 1×1 的巧克力
//x表示切除的正方形边长,x越大,分的个数越少,反之越大
while(l<r)//要找到一个最大的边长x,使得分的块数满足k个,这就是二分思路的由来
{
int mid = l+r + 1>>1;//由于找最大,使用向右二分模板
if(check(mid)) l = mid;//如果可以分k块,则向右找,增大边长,减少块数,寻找更大的满足题意的边长
else r = mid - 1;//否则减少边长,增大块数
}
cout<<l<<endl;
return 0;
}
二、前缀和
1230. K倍区间
解题思路:
- 首先,对于这种求一个数列某一段和的问题,显然用到前缀和的处理方法
- 第二个需要解决的问题就是如何确定某一段数能不能被K整除,能够被K整除,意味着余数为0,所以一段数与K取模得0就说明这段数是要找的K倍区间
- 可以从前往后枚举,设当前枚举到第位,且它的前缀和sum[i]与K取模为m,如果在之前的第jj位前缀和同样是与K取模得m的话,sum[i]−sum[j]sum[i]−sum[j]也就是Aj+1…Ai这段数与K取模为00,这一段就可以计入答案了,对于任意一个,只需要统计前面有多少个这样的jj即可
- 具体的做法就是开一个计数数组cnt[],cnt[i]就是计算余数为i的前缀和有多少个
- 另外要注意一点是,假如某一段数A1…AiA1…Ai,不需要减去区间,它本身就是一个K倍区间,但cnt[0]cnt[0]保存的是之前余数为00的前缀和的个数,这个区间本身没有被算进去,所以初始状态下,cnt[0]cnt[0]应该赋值为1
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)