二分与前缀和

数的范围

题目大意
给定一个按照升序排列的长度为n的整数数组,以及 q 个查询。对于每个查询,返回一个元素k的起始位置和终止位置(位置从0开始计数)。如果数组中不存在该元素,则返回“-1 -1”。
输入格式
第一行包含整数n和q,表示数组长度和询问个数。
第二行包含n个整数(均在1~10000范围内),表示完整数组。
接下来q行,每行包含一个整数k,表示一个询问元素。
输出格式
共q行,每行包含两个整数,表示所求元素的起始位置和终止位置。
如果数组中不存在该元素,则返回“-1 -1”。
数据范围
1≤n≤100000
1≤q≤10000
1≤k≤10000
输入样例

6 3
1 2 2 3 3 4
3
4
5

输出样例

3 4
5 5
-1 -1

解题思路
裸题二分,二分查找左端点和右端点
整数二分的步骤:
1.找一个区间 [L,R],使得答案一定在该区间中。
2.找一个判断条件,使得该判断条件具有二段性,并且答案一定是该二段性的分界点。
3.分析终点 M 在该判断条件下是否成立,如果成立,考虑答案在哪个区间;如果不成立,考虑答案在哪个区间。
4.如果更新方式写的是 R = Mid,不用做任何处理;如果更新方式是 L = Mid,则需要在计算Mid时 +1。

#include <iostream>
#include <cstdio>
using namespace std;
const int maxn = 1e5 + 7;
int a[maxn];
int main()
{
    int n,q;
    scanf("%d %d",&n,&q);
    for(int i=0;i<n;++i) scanf("%d",&a[i]);
    
    while(q--)
    {
        int x;
        scanf("%d",&x);

        int l=0,r=n-1;
        //查找左端点
        while(l<r)
        {
            int mid=l+r>>1;
            if(a[mid]>=x) r=mid;
            else l=mid+1;
        }
        if(a[l]!=x) printf("-1 -1\n");
        else
        {
            printf("%d ",l);
            r=n-1;
            //查找右端点
            while(l<r)
            {
                int mid=l+r+1>>1;
                if(a[mid]<=x) l=mid;
                else r=mid-1;
            }
            printf("%d\n",l);
        }
    }
    return 0;
}

数的三次方根

题目大意
给一个浮点数n,求它它的三次方根
输入格式
共一行,包含一个浮点数n。
输出格式
共一行,包含一个浮点数,表示问题的解。
注意,结果保留6位小数。
数据范围
−10000≤n≤10000−10000≤n≤10000
输入样例

1000.00

输出样例

10.000000

解题思路
实数域上二分

#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
    double l=-10000,r=10000,x;
    scanf("%lf",&x);
    while(l+1e-8<r)
    {
        double mid=(l+r)/2;
        if(mid*mid*mid>=x) r=mid;
        else l=mid;
    }
    printf("%f",l);
    return 0;
}

前缀和

题目大意
输入一个长度为n的整数序列。
接下来再输入m个询问,每个询问输入一对l, r。
对于每个询问,输出原序列中从第l个数到第r个数的和。
输入格式
第一行包含两个整数n和m。
第二行包含n个整数,表示整数数列。
接下来m行,每行包含两个整数l和r,表示一个询问的区间范围。
输出格式
共m行,每行输出一个询问的结果。
数据范围
1≤l≤r≤n1≤l≤r≤n,

1≤n,m≤1000001≤n,m≤100000,

−1000≤数列中元素的值≤1000
输入样例

5 3
2 1 3 6 4
1 2
1 3
2 4

输出样例

3
6
10

解题思路
每读入一个数,你就累计加前一个数,最后求得区间和就是a[r] - a[l-1]的差

#include <iostream>
#include <cstdio>
#include <stdio.h>
using namespace std;
const int maxn=1e5+7;
typedef long long ll;
ll a[maxn];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]+=a[i-1];
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%lld\n",a[r]-a[l-1]);
    }
    return 0;
}

子矩阵的和

题目大意
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标。
对于每个询问输出子矩阵中所有数的和。
输入格式
第一行包含三个整数n,m,q。
接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含四个整数x1, y1, x2, y2,表示一组询问。
输出格式
共q行,每行输出一个询问的结果。
数据范围
1≤n,m≤10001≤n,m≤1000,
1≤q≤2000001≤q≤200000,
1≤x1≤x2≤n1≤x1≤x2≤n,
1≤y1≤y2≤m1≤y1≤y2≤m,
−1000≤矩阵内元素的值≤1000
输入样例

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

解题思路
S[i][j]=S[i-1][j]+S[i][j-1]-S[i-1][j-1]+a[i][j]
边长为R的正方形,得出结论:
∑ x = i − R + 1 n ∑ y = j − R + 1 n A [ x ] [ y ] = S [ i , j ] − S [ i − R , j ] − S [ i , j − R ] + S [ i − R , j − R ] \sum_{x=i-R+1}^n \sum_{y=j-R+1}^n A[x][y]=S[i,j]-S[i-R,j]-S[i,j-R]+S[i-R,j-R] x=iR+1ny=jR+1nA[x][y]=S[i,j]S[iR,j]S[i,jR]+S[iR,jR]

#include <iostream>
#include <cstdio>
#include <stdio.h>
using namespace std;
const int maxn=1e3+7;
typedef long long ll;
ll a[maxn][maxn],ans[maxn][maxn];
int main()
{
    int n,m,q;
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%lld",&a[i][j]);
            ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1]+a[i][j];
        }
    }

    while(q--)
    {
        int x,y,tx,ty;
        scanf("%d%d%d%d",&x,&y,&tx,&ty);
        printf("%lld\n",ans[tx][ty]-ans[x-1][ty]-ans[tx][y-1]+ans[x-1][y-1]);
    }
    return 0;
}

机器人跳跃问题

题目大意
机器人正在玩一个古老的基于DOS的游戏。
游戏中有N+1座建筑——从0到N编号,从左到右排列。
编号为0的建筑高度为0个单位,编号为 i 的建筑高度为H(i)个单位。
起初,机器人在编号为0的建筑处。
每一步,它跳到下一个(右边)建筑。
假设机器人在第k个建筑,且它现在的能量值是E,下一步它将跳到第k+1个建筑。
如果H(k+1)>E,那么机器人就失去H(k+1)-E的能量值,否则它将得到E-H(k+1)的能量值。
游戏目标是到达第N个建筑,在这个过程中能量值不能为负数个单位。
现在的问题是机器人至少以多少能量值开始游戏,才可以保证成功完成游戏?
输入格式
第一行输入整数N。
第二行是N个空格分隔的整数,H(1),H(2),…,H(N)代表建筑物的高度。
输出格式
输出一个整数,表示所需的最少单位的初始能量值上取整后的结果。
数据范围
1≤N,H(i)≤ 1 0 5 10^{5} 105
输入样例

3
1 6 4

输出样例

3

解题思路
二分查找,每次变换都是 2E - H(k),二分查找满足条件的最小能量数值x,满足条件时调整右边边区间的范围,不满足时调整左边的区间范围,直到 l == r 时,输出结果。

#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn];
bool judge(ll e,int n)
{
    for(int i=1;i<=n;++i)
    {
        e=(e<<1)-a[i];
        if(e>1e5) return true;  //剪枝
        if(e<0) return false;    
    }
    return true;
}
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
    
    int l=0,r=1e5;
    while(l<r)
    {
        int mid=l+r>>1;
        if(judge(mid,n)) r=mid;
        else l=mid+1;
    }
    printf("%d\n",l);
    return 0;
}

四平方和

题目大意
四平方和定理,又称为拉格朗日定理:
每个正整数都可以表示为至多 4 个正整数的平方和。
如果把 0 包括进去,就正好可以表示为 4 个数的平方和。
比如:
5=0 2 ^2 2 + 0 2 ^2 2 + 1 2 ^2 2 + 2 2 ^2 2
7=1 2 ^2 2 + 1 2 ^2 2 + 1 2 ^2 2 + 2 2 ^2 2
对于一个给定的正整数,可能存在多种平方和的表示法。
要求你对 4 个数排序:0≤a≤b≤c≤d
并对所有的可能表示法按 a,b,c,d
为联合主键升序排列,最后输出第一个表示法。
输入格式
输入一个正整数 N
输出格式
输出4个非负整数,按从小到大排序,中间用空格分开。
数据范围
0<N<5∗10 6 ^6 6
输入样例

5

输出样例

0 0 1 2

解题思路
暴力思路O(n 3 ^3 3)

 for(int i=0;i*i<n;++i)
        for(int j=i;i*i+j*j<n;++j)
            for(int k=j;i*i+j*j+k*k<n;++k)
            {
                int t=n-i*i-j*j-k*k;
                int d=sqrt(t);
                if(d*d==t){
                    printf("%d %d %d %d\n",i,j,k,d);
                    return 0;
                }
            }

二分优化:
先把 c 2 ^2 2+d 2 ^2 2存起来,然后枚举 a 和 b,二分查找(n-a ∗ * a - b ∗ * b)=node[i].sum的值,当 l = r 时,输出结果

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=5e6+7;
struct Node
{
    int sum,c,d;
    bool operator < (const Node &t)const
    {
        if(sum!=t.sum) return sum<t.sum;
        if(c!=t.c) return c<t.c;
        return d<t.d;
    }
    
}node[maxn];
int main()
{
    int idx=0,n;
    scanf("%d",&n);
    for(int c=0;c*c<=n;++c)
        for(int d=c;d*d+c*c<=n;++d)
            node[idx++]={c*c+d*d,c,d};
        
    sort(node,node+idx);    
    for(int a=0;a*a<n;++a)
        for(int b=a;b*b+a*a<n;++b)
        {
            int t=n-a*a-b*b;
            int l=0,r=idx-1;
            while(l<r)
            {
                int mid=l+r>>1;
                if(node[mid].sum>=t) r=mid;
                else l=mid+1;
            }
            if(node[l].sum==t) {
                printf("%d %d %d %d\n",a,b,node[l].c,node[l].d);
                return 0;
            }
        }
    return 0;
}

分巧克力

题目大意
儿童节那天有 K 位小朋友到小明家做客。
小明拿出了珍藏的巧克力招待小朋友们。
小明一共有 N 块巧克力,其中第 i 块是 H i _i i×W i _i i的方格组成的长方形。
为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。
切出的巧克力需要满足:
1.形状是正方形,边长是整数
2.大小相同
例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者2 块 3×3 的巧克力。
当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含两个整数 H i _i i 和 W i _i i
输入保证每位小朋友至少能获得一块 1×1的巧克力。
输出格式
输出切出的正方形巧克力最大可能的边长。
数据范围
1≤N,K≤10 5 ^5 5
1≤Hi,Wi≤10 5 ^5 5
输入样例

2 10
6 5
5 6

输出样例

2

解题思路
二分蛋糕在能够切出 k 块蛋糕的最大尺寸。

#include <cstdio>
using namespace std;
const int maxn=1e5+7;
int a[maxn],b[maxn];
bool judge(int n,int k,int mid)
{
    int res=0;
    for(int i=0;i<n;++i)
    {
        res+=(a[i]/mid)*(b[i]/mid);
        if(res>=k) return true;
    }
    return false;
}
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0;i<n;i++) scanf("%d %d",&a[i],&b[i]);
    int l=0,r=1e5+7;
    while(l<r)
    {
        int mid=l+r+1>>1;
        if(judge(n,k,mid)) l=mid;
        else r=mid-1;
    }
    printf("%d\n",l);
    return 0;
}

激光炸弹

题目大意
地图上有 N 个目标,用整数 X i _i i,Y i _i i表示目标在地图上的位置,每个目标都有一个价值 W i _i i
注意:不同目标可能在同一位置。
现在有一种新型的激光炸弹,可以摧毁一个包含 R×R
个位置的正方形内的所有目标。
激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y
轴平行。
求一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式
第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。
接下来 N 行,每行输入一组数据,每组数据包括三个整数 X i _i i, Y i _i i,W i _i i,分别代表目标的 x 坐标,y坐标和价值,数据用空格隔开。
输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。
数据范围
0≤R≤10 9 ^9 9
0<N≤10000
0≤Xi,Yi≤5000
0≤Wi≤1000
输入样例

2 1
0 0 1
1 1 1

输出样例

1

解题思路
前缀和处理区间,最后求最值。

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=5e3+7;
int ans[maxn][maxn];
int main()
{
    int n,r,mx,my;
    scanf("%d%d",&n,&r);
    mx=my=r;
    while(n--)
    {
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w);
        ++x,++y;
        mx=max(mx,x),my=max(my,y);
        ans[x][y]+=w;
    }
    //预处理前缀和
    for(int i=1;i<=mx;++i)
       for(int j=1;j<=my;++j) 
            ans[i][j]=ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1]+ans[i][j];
           
    int res=0;
    for(int i=r;i<=mx;++i)
        for(int j=r;j<=my;++j)
            res=max(res,ans[i][j]-ans[i-r][j]-ans[i][j-r]+ans[i-r][j-r]);
    printf("%d\n",res);
    return 0;
}

k倍区间

题目大意
给定一个长度为 N 的数列,A 1 _1 1,A 2 _2 2,…A N _N N
,如果其中一段连续的子序列 A i _i i,A i _i i+1,…A j _j j之和是 K 的倍数,我们就称这个区间 [i,j]
是 K倍区间。
你能求出数列中总共有多少个 K 倍区间吗?
输入格式
第一行包含两个整数 N 和 K。
以下 N 行每行包含一个整数 A i _i i
输出格式
输出一个整数,代表 K 倍区间的数目。
数据范围
1≤N,K≤100000,
1≤A i _i i≤100000
输入样例

5 2
1
2
3
4
5

输出样例

6

解题思路
暴力思路,时间复杂度O(n 2 ^2 2)

nt res=0;
for(int R=1;R<=n;R++)
for(int L=1;L<=R;L++)
{
    int sum=s[R]-s[L-1];
    if(!(sum%k)) res++;
}

当 R 固定时,在 0 ~R-1之间能找到多少个满足
(s[R]-s[L-1])%k==0
时间复杂度O(n)代码

#include <cstdio>
using namespace std;
const int maxn=1e5+7;
typedef long long ll;
ll a[maxn],cnt[maxn];
int main()
{
    int n,k;
    ll res=0;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        a[i]=(a[i-1]+a[i])%k;
        res+=cnt[a[i]];
        cnt[a[i]]++;
    }
    printf("%lld\n",res+cnt[0]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
这是一个比较经典的问题,可以使用前缀积+二分的方法解决。 具体来说,我们可以先求出序列 a 的前缀积数组 prefix,即 prefix[i] 表示前 i 个数的乘积。然后对于每个右端点 r,我们可以二分出最小的左端点 l,使得 prefix[r]/prefix[l-1] 的末尾有 k 个零。这个二分的过程可以先二分出 prefix[r] 除以 2 的幂次,再二分出 prefix[r] 除以 5 的幂次,最后求出这两个幂次的较小值。 时间复杂度为 O(n log n)。 以下是用 C++ 实现的代码: ```c++ #include <iostream> #include <vector> using namespace std; typedef long long LL; const int MAXN = 2e5 + 5; int n, k, ans; vector<int> pos[MAXN]; LL a[MAXN], prefix[MAXN]; int main() { cin >> n >> k; for (int i = 1; i <= n; i++) { cin >> a[i]; prefix[i] = prefix[i - 1] + a[i]; } pos[0].push_back(0); for (int i = 1; i <= n; i++) { int cnt2 = 0, cnt5 = 0; LL tmp = prefix[i]; while (tmp % 2 == 0) tmp /= 2, cnt2++; while (tmp % 5 == 0) tmp /= 5, cnt5++; if (cnt2 >= k) ans++; // 整个前缀和都满足要求 if (cnt2 >= k || cnt5 >= k) { int target = cnt2 >= k ? cnt2 - k : 0; for (int j : pos[target]) { if (j < i) ans++; // 子区间满足要求 } pos[cnt2].push_back(i); } } cout << ans << endl; return 0; } ``` 其中 pos[i] 表示前缀和中末尾有 i 个 0 的位置的集合。我们遍历前缀和数组 prefix,对于每个位置 i,我们计算出 prefix[i] 末尾的 2 的幂次和 5 的幂次,分别记为 cnt2 和 cnt5。如果 cnt2 大于等于 k,则整个前缀和都满足要求,因此可以直接将 ans 加 1。如果 cnt2 小于 k 且 cnt5 小于 k,则该前缀和不满足要求,不需要做任何处理。如果 cnt2 大于等于 k 或 cnt5 大于等于 k,则我们需要找到前缀和中末尾恰好有 cnt2 - k 或 cnt5 - k 个 0 的位置 j,然后对于每个位置 j,子区间 [j+1, i] 的乘积末尾恰好有 k 个 0,因此可以将 ans 加上 j 的个数。最后,我们将位置 i 加入 pos[cnt2] 中,以便后续计算。 时间复杂度为 O(n log n)。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

幸愉聊信奥

谢谢亲的支持,我会继续努力啦~

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

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

打赏作者

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

抵扣说明:

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

余额充值