二分与前缀和

二分

在这里插入图片描述

一. 机器人跳跃问题

Acwing730. 机器人跳跃问题
机器人正在玩一个古老的基于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)≤105,

输入样例1:

5
3 4 3 2 4

输出样例1:

4

输入样例2:

扫描二维码关注公众号,回复: 13112071 查看本文章
3
4 4 4

输出样例2:

4

输入样例3:

3
1 6 4

输出样例3:

3
  1. 左右边界分别是l=0,r=maxH=0,r=maxH。
  2. 题目说如果H(k+1)>E,那么机器人就失去H(k+1)−E的能量值,否则它将得到E−H(k+1)的能量值,那我们就可以写一个check函数,
  3. 其中判断条件就是任何一步的能量值都不能小于0,如果小于0就返回false,如果其中有任何一步使得能量大于最大高度返回true,否则就返回true。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int n, q[N];
int maxH = -1;
bool check(int x)
{
    
    
    int tmp = x;
    for(int i = 0; i < n; i++)
    {
    
    
        tmp += tmp - q[i];
        if(tmp < 0) return false;
        if(tmp >= maxH) return true;
    }
    return true;
    
}
int main()
{
    
    
    cin >>n;
    
    for(int i = 0; i < n; i++) 
    {
    
    
        cin >> q[i];
        maxH = max(maxH, q[i]);
    }
    int l = 0, r = maxH;
    
    while(l < r)
    {
    
    
        int mid = l + r >> 1;
        if(check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l;
    return 0;
}

二. 四平方和

Acwing1221. 四平方和
四平方和定理,又称为拉格朗日定理:

每个正整数都可以表示为至多 4 个正整数的平方和。

如果把 0 包括进去,就正好可以表示为 4 个数的平方和。

比如:
5=02+02+12+22
7=12+12+12+22

对于一个给定的正整数,可能存在多种平方和的表示法。

要求你对 4 个数排序:

0≤a≤b≤c≤d
并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法。

输入格式
输入一个正整数 N。

输出格式
输出4个非负整数,按从小到大排序,中间用空格分开。

数据范围
0<N<5∗106
输入样例:

5

输出样例:

0 0 1 2
考虑时间复杂度
1.最多只能枚举2个数
2.用空间换时间
三重循环(暴力枚举):
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int N = 2500010;
int n;
int main()
{
    
    
    cin >> n;
    for(int a = 0; a *a <= n; a++)
      for(int b = a; a*a + b*b <= n;b++)
        for(int c = b; a*a + b * b + c * c <= n;c++)
        {
    
    
            int t = n -a*a - b*b -c * c;
            int d = sqrt(t);
            if(d * d == t)
            {
    
    
                printf("%d %d %d %d\n" , a,b,c,d);
                return 0;
            }
        }
}
二分:
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
/*考虑时间复杂度
1.最多只能枚举2个数
2.用空间换时间*/
const int N = 2500010;
int n,m;
struct Sum
{
    
    
    int s,c,d;
    bool operator<(const Sum &t) const
    {
    
    
        if(s != t.s) return s < t.s;
        if(c != t.c) return c <t.c;
        return d < t.s;
    }
}sum[N];
int main()
{
    
    
    cin >>n;
    for(int c = 0; c *c<= n; c++)
     for(int d = c; c*c + d*d<= n; d++)
      sum[m++] = {
    
    c*c + d * d, c,d};
     
    sort(sum, sum+m);
    
    for(int a = 0;a*a<= n; a++)
     for(int b = 0; a*a + b*b <= n; b++)
     {
    
    
         int t = n - a*a - b*b;
         int l = 0,r = m -1;
         while(l< r)
         {
    
    
             int mid = l + r >> 1;
             if(sum[mid].s >= t) r = mid;
             else l = mid+1;
         }
         if(sum[l].s == t)
         {
    
    
             printf("%d %d %d %d\n", a, b,sum[l].c, sum[l].d);
             return 0;
         }
     }
     return 0;
}

三.分巧克力

Acwing1227. 分巧克力
儿童节那天有 K 位小朋友到小明家做客。

小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N 块巧克力,其中第 i 块是 Hi×Wi 的方格组成的长方形。

为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。

切出的巧克力需要满足:

形状是正方形,边长是整数
大小相同
例如一块 6×5 的巧克力可以切出 6 块 2×2 的巧克力或者 2 块 3×3 的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入格式
第一行包含两个整数 N 和 K。

以下 N 行每行包含两个整数 Hi 和 Wi。

输入保证每位小朋友至少能获得一块 1×1 的巧克力。

输出格式
输出切出的正方形巧克力最大可能的边长。

数据范围
1≤N,K≤105,
1≤Hi,Wi≤105
输入样例:

2 10
6 5
5 6

输出样例:

2
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int H[N], W[N];
int maxH = -1;
int n, k;
bool check(int x)
{
    
    
    int cnt = 0;
    for(int i = 0; i < n; i++)
    {
    
    
            cnt += (H[i]/x) * (W[i]/ x); //计算巧克力能分到的个数
    }
    if(cnt >= k) return true;
    else return false;
}
int main()
{
    
    
    
    cin >> n >> k;
   for(int i = 0; i < n; i++) 
   {
    
    
       cin >> H[i] >> W[i];
   }
   int l = 0, r = 100000;
   while(l < r)
   {
    
    
       int mid = l + r + 1 >> 1;
       if(check(mid)) l = mid;//求最大值模板
       else r = mid - 1;
   }
   cout << l;
   return 0;
   
}

前缀和

一.激光炸弹

ACWing99. 激光炸弹
地图上有 N 个目标,用整数Xi,Yi表示目标在地图上的位置,每个目标都有一个价值Wi。

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和x,y轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式
第一行输入正整数 N 和 R ,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。

接下来N行,每行输入一组数据,每组数据包括三个整数Xi,Yi,Wi,分别代表目标的x坐标,y坐标和价值,数据用空格隔开。

输出格式
输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围
0≤R≤109
0<N≤10000,
0≤Xi,Yi≤5000
0≤Wi≤1000
输入样例:

2 1
0 0 1
1 1 1

输出样例:

1

解法思路:我们首先观察题目,可以建立一个数组f[i][j]表示坐标为(xi,yj)上的权值,那么我们接着思考,因为题目上面说了要求算出这个边长为r的正方形面积,而且整个题目中,只有查询操作,没有修改操作,且内存只要略微省着用,就可以满足O(n2)的条件,所以我们可以确认这一题可以使用二维前缀和。

#include<iostream>
#include<cstdio>
using namespace std;
int f[5010][5010],n,m,r,c,x,y,z,i,j,ans;
int main()
{
    
    
    cin>>n>>m;
    r=c=m;
    for(i=1; i<=n; i++)
    {
    
    
        scanf("%d%d%d",&x,&y,&z);
        x++,y++;
        f[x][y]=z;
        r=max(r,x);
        c=max(c,y);
    }
    for(i=1; i<=r; i++)
        for(j=1; j<=c; j++)
            f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+f[i][j];
    for(i=m; i<=r; i++)
        for(j=m; j<=c; j++)
            ans=max(ans,f[i][j]-f[i][j-m]-f[i-m][j]+f[i-m][j-m]);
    cout<<ans<<endl;
    return 0;
}

二.K倍区间

Acwing1230. K倍区间
给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j] 是 K 倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

输入格式
第一行包含两个整数 N 和 K。

以下 N 行每行包含一个整数 Ai。

输出格式
输出一个整数,代表 K 倍区间的数目。

数据范围
1≤N,K≤100000,
1≤Ai≤100000
输入样例:

5 2
1
2
3
4
5

输出样例:

6

分析:
求区间[l,r]的和是k的倍数的个数。求区间和,我们可以通过前缀和来求出。我们规定sum[i]表示第1个元素到第i个元素的和。那么sum[r] - sum[l-1]就是区间[l,r]的和。区间[l,r]的和是k的倍数即(sum[r] - sum[l-1])%k = 0 即sum[r]%k = sum[l-1]%k

#include<bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long ll;
int sum[N],a[N],res[N];
int n,k;
ll ans=0;
int main(){
    
    
    cin>>n>>k;
    for(int i=1;i<=n;i++){
    
    
        cin>>a[i];
        sum[i]=(sum[i-1]+a[i])%k;//前缀和取模
        ans+=res[sum[i]];//更新答案
        res[sum[i]]++;//两个相等的前缀和就能组成一个k倍区间
    }
    cout<<ans+res[0]<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/Annabel_CM/article/details/112663027