二分与前缀和

2023-09-20 15:14:32

目录

🍈前言

❤二分

🌹二分

🌼数的范围

🌼数的三次方根

🌼特殊数字

🌼机器人跳跃问题

🌼四平方和

🌼分巧克力

🌹前缀和

🌼前缀和

🌼子矩阵的和

🌼激光炸弹

🌼K倍区间


🍈前言

❤二分

整数二分模板中,一个比较坑的点,就是C++整数向下取整的机制,考虑到这点,你才能写出AC 100%的代码

关键在于

1,对if ()后面条件的判断 

2,条件判断后,是先 l = mid,还是先 r = mid,如果是 l = mid,上面 mid = (l + r + 1) >> 1

3,纸上画图分析 目标值 和 区间的关系

4,避开下取整的坑,就能防止死循环 或 查找不到目标点

步骤

(1)区间

(2)二段性

(3)分界点 

(整数二分边界:区间只剩1个数)

(实数二分边界:区间长度足够小)

🌹二分

🌼数的范围

789. 数的范围 - AcWing题库

一道整数二分的题 

AC  代码

#include<iostream>
using namespace std;

int main()
{
    int n, q, k;
    cin>>n>>q;
    int a[n + 1];
    for (int i = 0; i < n; ++i)
        cin>>a[i];
    
    for (int i = 0; i < q; ++i) {
        cin>>k;
        // 二分左端点
        int l = 0, r = n - 1; // 区间范围
        while (l < r) {
            int mid = (l + r) >> 1;
            if (a[mid] >= k) 
                r = mid;
            else
                l = mid + 1;
        }
        if (a[r] == k) {
            cout<<r<<' '; // 退出循环时 l == r
            // 二分右端点
            l = r, r = n - 1;
            while (l < r) {
                int mid = (l + r + 1) >> 1; // l = mid,考虑到向下取整,mid要先+1
                if (a[mid] <= k)
                    l = mid;
                else
                    r = mid - 1; // 目标值必然在r左边, 不包括r
            }
            cout<<r<<endl; // 退出循环时 l == r
        }
        else
            cout<<"-1 -1"<<endl;
        
    }
    
    return 0;
}

🌼数的三次方根

一道实数二分的题

有单调性一定可以二分,没有单调性也可能可以二分。

(1)确定区间

(2)二段性

(3)边界
(4)注意double以及精度 <1e-8,以便保证6位小数的精度

AC   代码

#include<iostream>
#include<cstdio>
using namespace std;


int main()
{
    double n;
    cin>>n;
    double l = -50, r = 50; // 50的三次方已经超过10000了, 主要左端点是负数
    while (r - l >= 1e-8) {
        double m = (l + r) / 2;
        if (m*m*m >= n) 
            r = m;
        else
            l = m;
    }
    printf("%.6lf", l);
    
    return 0;
}

🌼特殊数字

P1817 - [NewOJ Week 6] 特殊数字 - New Online Judge (ecustacm.cn)

1,两层for里最好遍历到  < 32,才能AC  100%,遍历到 < 31只能AC  67%

虽然2^30已经满足1e9了,不知道为什么不让过,总之范围大一点安全

2,先排序,再二分(因为二分需要二段性和边界)

3,用二分模板,最后需要分类讨论输入的 x 和数组中元素的大小对比

AC  代码

#include<iostream>
#include<algorithm>
using namespace std;

int a[1010], cnt; 

int main()
{
    // 预处理符合题目要求的二进制数字
    for (int i = 0; i < 32; ++i)
        for (int j = i + 1; j < 32; ++j) {
            a[cnt++] = (1 << i) + (1 << j);
        }
    // 先排序再二分
    sort(a, a + cnt);
    int t, x;
    cin>>t;
    while (t--) {
        cin>>x;
        // 二分
        int l = 0, r = cnt - 1; // 数组下标
        while (l < r) {
            int m = (l + r) >> 1;
            if (a[m] >= x)
                r = m;
            else
                l = m + 1;
        } // 退出循环后 l == r
        if (a[l] == x)
            cout<<0<<endl;
        else {
            int ans;
            if (a[l] > x)
                ans = (a[l] - x > x - a[l - 1]) ? x - a[l - 1] : a[l] - x;
            else
                ans = (x - a[l] > a[l + 1] - x) ? a[l + 1] - x : x - a[l];
            cout<<ans<<endl;
        }
    }

    return 0;
}

// 1 2 4 8 16 32
// 3 5 6 9 10 12 17 18 20 24 33 34 36 40 48

🌼机器人跳跃问题

730. 机器人跳跃问题 - AcWing题库

(1)

首先,为了确保二段性,我们用归纳法,可以得到,当初始能量 >= 某个值, 能量继续增大,还是满足条件

可以用二分

(2)

建议画图分析 while(l < r) 循环内部的操作

AC  代码

#include<iostream>
#include<cstdio>
using namespace std;

int n, h[100010];

bool check(int x)
{
    for (int i = 1; i <= n; ++i) {
        x = 2*x - h[i];
        if (x < 0) return false;
        if (x >= 1e5) return true; // 这里剪枝很重要,否则会爆long long
    }
    return true;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &h[i]);
    int l = 0, r = 100000;
    while(l < r) {
        int m = (l + r) >> 1;
        if (check(m)) r = m;
        else l = m + 1;
    }
    printf("%d", l);
    return 0;
}

🌼四平方和

1221. 四平方和 - AcWing题库

(1)每次做题前,先分析时间复杂度,本题  N <= 5e6,因为是平方,每个数最大 2400 

(2)考虑到一般空间复杂度要求 1e9,时间复杂度要求1e8,此处只能枚举2个数

(3)N = a^2 + b^2 + c^2 + d^2,枚举a,b后,d = 根号(N - a^2 - b^2 - c^2),还有个c需要通过空间换时间得到

(4)具体就是

for       a.......
    for         b......... {
        t = N - a^2 - b^2;
        // 二分: 检查t是否在前面出现过O(logn)
    }

二分前,需要将所有数存在数组中,然后排序

(5)题目中联合主键顺序,即字典序最小

(6)代码中结构体Sum中定义了运算符重载👇

operator< 函数是一个比较运算符,它定义了Sum类型之间的小于关系。当我们使用sort等算法对Sum类型的数组进行排序时,实际上是按照<号运算符的比较结果来确定元素之间的相对位置

字典序最小的解释

(1)由于a从0开始遍历,每一种符合a = 0的可能都会遍历到,所以a是最小的

(2)同理,b从a开始,b一定是第二小的

(3)最后的c, d,定义一个结构体,按 s, c, d优先级的顺序小到大即可

AC  代码

#include<iostream>
#include<algorithm> //sort()
using namespace std;

const int N = 2500000;

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.d;
    }
}Sum[N];

int main()
{
    int n, m = 0;
    cin>>n;
    // 空间换时间,预处理两个数的平方和
    for (int c = 0; c*c <= n; ++c)
        for (int d = c; d*d + c*c <= n; ++d)
            Sum[m++] = {c*c + d*d, c, d}; // 与定义处一一对应
    
    sort(Sum, Sum + m);
    
    // 枚举a, b
    for (int a = 0; a*a <= n; ++a)
        for (int b = a; a*a + b*b <= n; ++b) {
            // 二分: Sum中找c*c + d*d
            int t = n - a*a - b*b;
            int l = 0, r = m - 1; // Sum[]的下标
            while (l < r) {
                int mid = (l + r) >> 1;
                if (Sum[mid].s >= t) r = mid;
                else l = mid + 1;
            } // 退出循环时, l == r
            if (Sum[l].s == t) {
                cout<<a<<" "<<b<<" "<<Sum[l].c<<" "<<Sum[l].d<<endl;
                return 0;
            }
        }
        return 0;
}

🌼分巧克力

1227. 分巧克力 - AcWing题库

AC  代码

#include<iostream>
#include<cstdio> // scanf(), printf()
using namespace std;

const int N = 100005;
int n, k, h[N], w[N];

bool check(int m)
{
    int res = 0;
    for (int i = 0; i < n; ++i) {
        res += (h[i] / m) * (w[i] / m);
        if (res >= 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 = 0, r = 1e5;
    while (l < r) {
        int m = (l + r + 1) >> 1; // m表示边长
        if (check(m)) // check 表示个数
            l = m;
        else
            r = m - 1;
    }
    printf("%d", l);
    
    return 0;
}

🌹前缀和

🌼前缀和

795. 前缀和 - AcWing题库

AC  代码

#include<iostream>
#include<cstdio> 
using namespace std;

const int N = 100005;

int main()
{
    int n, m, a[N], s[N] = {0};
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
        s[i] += s[i - 1] + a[i];   
    }
    int l, r;
    while (m--) {
        scanf("%d%d", &l, &r);
        printf("%d\n", s[r] - s[l - 1]);
    }
    return 0;
}

🌼子矩阵的和

796. 子矩阵的和 - AcWing题库

画图,容斥原理

(1)预处理前缀和数组👇

S(i, j) = S(i - 1, j) + S(i, j - 1) - S(i - 1, j - 1) + a(i, j)

(2)利用前缀和数组计算ans

ans = S(x2 , y2) - S(x2, y1 - 1) - S(x1 - 1, y2) + S(x1 - 1, y1 - 1)

(3)为防止越界,下标从 1~n

AC  代码

#include<iostream>
#include<cstdio>

const int N = 1005;

int main()
{
    int n, m, q, a[N][N], s[N][N];
    scanf("%d%d%d", &n, &m, &q);
    // 预处理前缀和数组
    for (int i = 1; i <= n; ++i) 
        for (int j = 1; j <= m; ++j) {
            scanf("%d", &a[i][j]);
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    // 计算结果
    while (q--) {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
        printf("%d\n", s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1]);
    }
    return 0;
}

🌼激光炸弹

99. 激光炸弹 - AcWing题库

注意目标位置,可以理解为在点上。

在上题容斥原理的基础上,O( (n - R + 1) * (n - R + 1) ),枚举正方形右下角的点。

本题难点在于边界,而不是二维前缀和

再说说空间复杂度👇

5000 * 5000二维数组 int,= 2.5 * 10^7,两个二维数组a[][] 和 s[][] 就是 5 * 10^7

1 int = 4 byte

= 2 * 10^8 byte

1M = 1024 * 1024 byte

所以2个二维开到 2 * 10^8 / 10^6 = 200M > 168M

所以只能开1个 s[][]

tips: 为了防止初始越界,前缀和类型都从1开始

已经得到前缀和矩阵,如何计算ans,参考上题截图👇

AC  代码

有几个坑,详情看注释

#include<iostream>
#include<algorithm> //max()
using namespace std;

const int N = 5010;

int main()
{
    int cnt, R, s[N][N] = {0};
    cin>>cnt>>R;
    R = min(R, 5001); // 防止越界
    
    int x, y, w, n, m;
    
    n = m = R; // 防止 n, m 为0, 无法进入循环
    
    while (cnt--) {
        cin>>x>>y>>w;
        x++, y++; // 坐标1开始
        n = max(n, x), m = max(m, y); // 不越界
        s[x][y] += w;
    }
    // 预处理前缀和数组
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
            
    int ans = 0;
    // 枚举边长R正方形的右下角(i ,j)
    for (int i = R; i <= n; ++i)
        for (int j = R; j <= m; ++j)
            ans = max(ans, s[i][j] - s[i - R][j] - s[i][j - R] + s[i - R][j - R]);
    cout<<ans<<endl;
    
    return 0;
}

🌼K倍区间

1230. K倍区间 - AcWing题库

一维前缀和 + 同余

时间 O(n)

解释下同余👇

cnt[s[i] % k]++;     cnt[i] 统计余数为 i 的前缀和的个数

ans += cnt[i], 注意ans放前面,因为先出现余数相同的前缀和,才能 +=

同余意味着,( s[j] - s[i] ) % k == 0,满足题意

AC  代码

#include<iostream>
#include<cstdio>

const int N =  100005;
#define LL long long

int main()
{
    LL n, k, s[N] = {0}, cnt[N] = {0}, ans = 0; // LL防止爆int
    scanf("%lld%lld", &n, &k);
    
    // 读入 + 预处理前缀和
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &s[i]);
        s[i] += s[i - 1];
    }
    
    // 同余
    cnt[0] = 1; // 余数0直接加
    for (int i = 1; i <= n; ++i) {
        ans += cnt[s[i] % k]; // 先 ans+=
        cnt[s[i] % k]++; // 再cnt[]++
    }
    printf("%lld", ans);
    return 0;
}

更多推荐

Redis 面试题——缓存穿透、缓存击穿和缓存雪崩

目录1.缓存穿透2.缓存击穿3.缓存雪崩4.总结参考文章:缓存实战(1)缓存雪崩、缓存击穿和缓存穿透入门简介及解决方案1.缓存穿透(1)问题描述:缓存穿透是指在高并发场景下,大量的请求访问一个不存在于缓存中也不存在于数据库中的数据,导致每次请求都要查询数据库,增加了数据库的负载。通常发生在恶意攻击、频繁访问不存在的数据

OSCP系列靶场-Esay-Moneybox保姆级

OSCP系列靶场-Esay-Moneybox目录OSCP系列靶场-Esay-Moneybox总结准备工作信息收集-端口扫描目标开放端口收集目标端口对应服务探测信息收集-端口测试21-FTP端口的信息收集21-FTP版本版本信息21-FTP端口匿名登录测试(存在)21-FTP端口-文件GET收集21-FTP端口-PUT上

微服务 第一章 Java线程池技术应用

系列文章目录第一章Java线程池技术应用文章目录系列文章目录@[TOC](文章目录)前言1、Java创建线程方式回顾1.1、继承Thread类(只运行一次)1.1.1、改造成主线程常驻,每秒开启新线程运行1.1.2、匿名内部类1.1.3、缺点1.1.4、扩展知识:Java内部类1.1.4.1、静态内部类1.1.4.2、

机器学习实战:Python基于Ridge岭回归进行正则化(十三)

文章目录1.前言1.1岭回归的介绍1.2岭回归的应用2.自定义数据集实战演示2.1导入函数2.2创建数据集2.3alpha=0、1、10、100的分别情况3.Dushanbe_house数据集实战演示3.1导入函数和数据3.2剔除空值及可视化3.3整理数据3.4训练和测试数据集3.5评估数据集4.讨论1.前言1.1岭回

Python 04 之变量【列表,元组,集合,字典,字符串】

😀前言在Python编程语言中,我们经常会遇到各种数据类型和相应的操作方法。理解和掌握这些基本构造是进行有效编程的前提。在本文中,我们将介绍两种非常重要的数据结构-集合和字典,然后我们将深入探讨字符串及其相关的操作和处理方法,包括格式化和切片。我们还将通过示例来详细解释如何使用这些结构和方法,以便你可以在实际编程中轻

yolov5的使用

目录本地的标注数据集工具推荐如下:在线标注工具COCO训练模型用PyTorch训练一个简单的多层感知器(MLP)进行手写数字识别(MNIST数据集)。YOLOv5是一种流行的目标检测算法,可以用于识别和定位图像中的对象。要使用YOLOv5识别老鼠,您需要执行以下步骤:YOLOv5是一种流行的深度学习算法,用于目标检测和

金融业需要的大模型,是一个系统化工程

今年年初,在AIGC刚刚开始爆火的时候,我们曾经采访过一位AI领域的专家。当我们提问哪个行业将率先落地大模型时,他毫不犹豫地说道:“金融。”金融行业场景多、数据多、知识多,这样的“三多”特点让其成为AI大模型发挥价值的天选。与此同时,金融场景专业度高,业务复杂,在风控、安全、效率等方面有着严格的要求,初出茅庐的大模型,

GaussDB数据库SQL系列-数据去重

目录一、前言二、数据去重应用场景三、数据去重案例(GaussDB)1、示例场景描述2、定义重复数据3、制定去重规则4、创建测试数据(GaussDB)5、编写去重方法(GaussDB)6、附:全字段去重四、数据去重效率提升建议五、总结一、前言数据去重在数据库中是比较常见的操作。复杂的业务场景、多业务线的数据来源等等,都会

【深度学习】Pytorch 系列教程(十):PyTorch数据结构:2、张量操作(Tensor Operations):(4)索引和切片详解

目录一、前言二、实验环境三、PyTorch数据结构0、分类1、张量(Tensor)2、张量操作(TensorOperations)1.数学运算2.统计计算3.张量变形4.索引和切片使用索引访问单个元素使用切片访问子集使用索引和切片进行修改布尔索引(Booleanindexing)高级切片(Advancedslicing

以AI对抗AI,大模型安全的“进化论”

点击关注文丨刘雨琦,编|王一粟“互联网时代,我们是更危险,还是更安全?”2016年,互联网正值高速发展之际,电梯广告经常出现这几个大字,两行标语,从病毒木马到网络诈骗,对于安全的思考、安全防范技术的建立一直在与科技发展赛跑。同样,大模型时代发展的早期,也引发了许多安全考量。英特网被发明的十年后,互联网防护技术和产业链才

面试官:ES6中新增的Set、Map两种数据结构怎么理解?

🎬岸边的风:个人主页🔥个人专栏:《VUE》《javaScript》⛺️生活的理想,就是为了理想的生活!目录一、Set增删改查add()delete()has()clear()遍历二、Map增删改查sizeset()get()has()delete()clear()遍历三、WeakSet和WeakMapWeakSet

热文推荐