蓝桥杯备考(二分与前缀和)

例题

数的范围

输入样例:

6 3
1 2 2 3 3 4
3
4
5

输出样例:

3 4
5 5
-1 -1

思路:这是一个练习二分的好问题,他通过改变更新条件实现找一个数的左边界和右边界,本质上还是二分的模板

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100003;
int a[N];
int main()
{
    int n,q;
    cin>>n>>q;
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int l,r,mid,t;
    while(q--)
    {
        scanf("%d",&t);
        l=1,r=n;
        while(l<r)
        {
            mid=l+r>>1;
            if(a[mid]>=t) r=mid;
            else l=mid+1;
        }
        if(a[l]==t)
            printf("%d ",l-1);
        else
        {
            puts("-1 -1");
            continue;
        }
        l=1,r=n;
        while(l<r)
        {
            mid=l+r+1>>1;
            if(a[mid]<=t) l=mid;
            else r=mid-1;
        }
        printf("%d\n",l-1);
    }
    return 0; 
}

数的三次方根

 这是一个关于浮点型二分的问题,我们需要注意的一个问题就是边界的大小,如果不太确定边界的话就尽量使边界大一点,还有一个问题就是精度问题,不能太小,一般1e-7即可 

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
int main()
{
    double n;
    scanf("%lf",&n);
    double mid,l=-100,r=100;
    while(r-l>0.00000001)
    {
        mid=(l+r)/2;
        if(mid*mid*mid<n) l=mid;
        else r=mid;
    }
    printf("%lf",l);
    return 0;
}

前缀和

 这是一个前缀和的模板,必须要熟练理解并掌握

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100003;
int a[N],sum[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    int l,r;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&l,&r);
        printf("%d\n",sum[r]-sum[l-1]); 
    }
    return 0;
}

子矩阵的和

输入样例:

3 4 3
1 7 2 4
3 6 2 8
2 1 2 3
1 1 2 2
2 1 3 4
1 3 3 4

输出样例:

17
27
21

 这是二维前缀和的模板,必须要熟练掌握,最好画个图理解一下

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=1003;
int sum[N][N];
int main()
{
    int n,m,q;
    cin>>n>>m>>q;
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        scanf("%d",&sum[i][j]);
        sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
    }
    int x1,y1,x2,y2;
    while(q--)
    {
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%d\n",sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]);
    }
    return 0;
}

习题

机器人跳跃问题

输入样例1:

5
3 4 3 2 4

输出样例1:

4

输入样例2:

3
4 4 4

输出样例2:

4

输入样例3:

3
1 6 4

输出样例3:

3

 这是一道比较有技巧性的二分题目,首先不难看出这道题目可以用二分来做,因为每次对于能量E而言,他度过h[i]的建筑时能量会变为2*E-h[i],也就是如果答案为ans,那么比ans大的所有数都能够平安度过,所以答案具有单调性,我们可以用二分来解决,但是有一个特别需要注意的点就是2*E-h[i]在极端情况下是一个指数增长的表达式,根本就存不了,但是我们需要发现一个特点,就是当E大于之后建筑的最大值了那么一定能平安通过,这个时候可以来个剪枝,这样就能够ac了

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=100003;
long long h[N];
int n;
bool check(long long x)
{
    for(int i=1;i<=n;i++)
    {
        x+=x-h[i];
        if(x>=1e5) return true;//非常重要的剪枝
        if(x<0) return false;
    }
    return true;
}
int main()
{
    cin>>n;
    long long l=0,r=0,mid;//此处要开long long,否则在check函数中会溢出 
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&h[i]);
        r=max(r,h[i]);//所有建筑的最大值一定满足题意 
    }
    while(l<r)
    {
        mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    printf("%lld",l);
    return 0; 
}

四平方和

输入样例:

5

输出样例:

0 0 1 2

 对于这道题我们肯定不能直接暴力去做,o(n^4)肯定是会超时的,我们可以先将两个数所能组成的平方和先存下来,看看所给的数能否用我们所存储的两个数表示即可,这样复杂度就变为了o(n^2)

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath> 
using namespace std;
int main() 
{
    int num;
    cin>>num;
    int n=sqrt(num);
    map<int,int>m;
    for(int i=0;i<=n;i++)
        for(int j=i;j<=n;j++)
            m[i*i+j*j]=1;
    for(int i=0;i<=n;i++) 
        for(int j=i;j<=n;j++)
		{
            if(m[num-i*i-j*j]!=1) continue;
            for(int k=j;k<=n;k++)
			{
                double temp=sqrt(num-i*i-j*j-k*k);
                if(temp==(int)temp) 
				{
                    cout<<i<<" "<<j<<" "<<k<<" "<<(int)temp;
                    return 0;
                }
            }
        }
    return 0;
}

分巧克力

输入样例:

2 10
6 5
5 6

输出样例:

2

 如果边长为a的巧克力可以满足题意,则边长为a-1的巧克力更可以满足题意,所以巧克力的边长具有单调性,我们可以用二分来做,还有一点就是注意理解一块长为a,宽为b的巧克力最多可以分成(a/c)*(b/c)块边长为c的巧克力,知道了这个之后就可以写一个复杂度为o(n)的check函数,详情见代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100003;
int a[N],b[N];
int main()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)
        scanf("%d%d",&a[i],&b[i]);
    int l=1,mid,r=1000000000;
    while(l<r)
    {
        mid=l+r+1>>1;
        int cnt=0;
        for(int i=1;i<=n;i++)
            cnt+=((a[i]/mid)*(b[i]/mid));
        if(cnt>=k) l=mid;
        else r=mid-1;
    }
    printf("%d",l);
    return 0;
}

激光炸弹

输入样例:

2 1
0 0 1
1 1 1

输出样例:

1

 这是一个比较基础的二维前缀和问题,需要注意的就是边界问题以及特殊情况,边界问题就是我们在处理二维前缀和问题时最好使数组下标从1开始避免一些不必要的麻烦,特殊情况就是当r大于5001时,为了能适用于我们的代码,我们可以将其赋值为5001,不影响答案,也不用修改我们之前写过的代码

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
using namespace std;
const int N=5003;
int s[N][N];
int main()
{
    int n,r;
    cin>>n>>r;
    if(r>5001) r=5001;//将非常规数据转换为常规数据 
    int x,y,w;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&x,&y,&w);
        x++;y++;//注意下标从1开始方便运算 
        s[x][y]+=w;//同一个位置可能有多个物品
    }
    for(int i=1;i<=5001;i++)
    for(int j=1;j<=5001;j++)
        s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
     int ans=0;
     for(int i=r;i<=5001;i++)
     for(int j=r;j<=5001;j++)
        ans=max(ans,s[i][j]-s[i-r][j]-s[i][j-r]+s[i-r][j-r]);
     printf("%d",ans);  
    return 0;
}

K倍区间

 区间[l,r]时k倍区间当且仅当(sum[r]-sum[l-1])%k=0,也就是sum[r]和sum[l-1]模k所得余数相同,那么我们就可以预处理出来前缀和对k的余数,然后从前往后遍历,假如说遍历到第i个数,有sum[i]%k=p,看看这个数之前有多少数的前缀和对k取余也为p,就计入答案,知道遍历完整个前缀和数组即可

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100003;
int a[N];//记录前缀和 
int sum[N];//sum[i]记录遍历到当前位置对k取余得到值为i的区间有多少个 
int main()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i]=(a[i]+a[i-1])%k;
    }
    long long ans=0;//注意此处要开longlong防止极端情况,类似于100000个数全为0 
    sum[0]=1;//考虑到整个前缀和是k的倍数的情况 
    for(int i=1;i<=n;i++)
    {
        ans+=sum[a[i]];
        sum[a[i]]++;
    }
    printf("%lld",ans);
    return 0; 
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值