当前位置: 首页 > news >正文

【蓝桥杯】每日连续 Day9 前缀和

目录

前言

最佳牛围栏

分析

代码

壁画

分析

代码

K倍区间

分析

代码

统计子矩阵

分析

代码

递增三元组

分析

代码

激光炮弹

分析

代码

总结


前言

虽迟但到,主播今天去打了一下蓝桥杯的月赛,只ac了三道,鉴定为省三QAQ

今天为大家带来了一道二分的题目和三道前缀和的题目,话不多说我们马上开始吧。


最佳牛围栏


分析

看见牛就知道来自USACO,就知道题目不会很简单。

发现问题可以用遍历来解决,考虑一下能不能二分

先来尝试二分围起来的田地数量,很显然不满足单调性。

随后我们来尝试二分答案,能否二分平均值然后判断能否满足条件呢?

我们假设存在最大平均值m

很明显对于平均值小于等于m的情况皆可以满足,而对于平均值大于m的情况则不可以满足(这道题因为max是一种包含关系,所以可以用二分)。

所以我们通过二分来引入平均值m

接下来只需要判断是否存在平均值大于等于m即可。

但若挨个计算区间的平均值的话很显然是会超时的,需要优化。

如何来优化呢?

第一步,我们先将平均值计算的一步优化:

对于计算平均值显然是不可以直接使用前缀和的,我们列出式子

S / n <= m

移项

S <= m * n

S拆分

a1 + a2 + ... + an <= m + m + ... + m

每一项减去m

a1 - m + a2 - m + ... + an - m <= 0

因为每次的m都是确定的,属于离线问题,所以可以通过前缀和来优化

第二步,对区间枚举进行优化:

假设我们选定区间s,根据题目要求s的长度是要大于等于k的,我们将他转换成前缀减前缀

sj - si,为方便大家理解我画了图出来:

因为max是一种包含关系,所以我们不需要将每个区间的确切值算出来比较,只需要判断大小便好。

借助动态规划的思想,我们取min(si),即所有区间长度小于等于i的所有前缀的最小值。随后判断sj - min(si)正负便好。


代码

// 二分答案
#include<iostream>
using namespace std;
const int N = 100010;
const double INF = 1e-6;
int n, f;
int nums[N];
double s[N];

bool func(double x)
{
    //cout << x << endl;
    for(int i = 1; i <= n; i++)
    {
        s[i] = s[i - 1] + nums[i] - x;
        //cout << s[i] << ' ';
    }
    //puts("");
    double l = 0;
    for(int i = 1; i <= n - f + 1; i++)
    {
        //cout << l << ' ';
        l = min(l, s[i - 1]); //寻找最小前缀
        if(s[i + f - 1] - l >= 0) //存在至少一种可能可以使得区间大于0
            return true;
    }
    //puts("");
    return false;
}

int main()
{
    scanf("%d%d", &n, &f);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", nums + i);
    }
    
    double l = 0, r = 2000;
    while(r - l > INF)
    {
        double x = (l + r) / 2;
        if(func(x)) //可以满足
            l = x;
        else //不能满足
            r = x;
    }
    printf("%d", (int)(r * 1000));
    return 0;
}

壁画


分析

这其实是一道思维题。先来给大家叙述一遍题目

n段连续的墙体。

t第一次可以任选一个地方画画,但是在之后只能选择与已经画了的地方的相邻位置画画(也就是一个连续的区间)。

每天结束后都会有一段墙面崩溃,且只会崩溃只与一段还未毁掉的墙面相邻的墙面(其实就是前缀和后缀)。

且崩溃无法蔓延到已经画过画的部分。

先来考虑极限情况,第一次在端点作画。

由于t是先手,所以这种情况t可以画画ceil(n/2)次。

接下来我们来思考一般情况,来分析是不是一定可以画到ceil(n/2)次t是绝顶聪明的,只判断可行性。)

我们假设t画到了ceil(n/2)次,如图:

对两边画出沿交界点对称的区间。

第一次选择两个区间的中间进行画画,随后我们可以发现只要我们每次选择摧毁的那个方向画画,最后就一定能画满ceil(n/2)次。

所以我们只需要查找所有长度为ceil(n/2)的区间的最大值即可。

要进行离线的区间和操作,使用前缀和


代码

// 毁坏程度与绘画天数相关联,枚举起始位置
#include<iostream>
using namespace std;
const int N = 5000010;
char a[N];
int s[N];
int t, n, l;

int func(int x) //根据初始位置计算可行天数
{
    int l = (n + 1)/2;
    return s[x + l - 1] - s[x - 1];
}

int main()
{
    scanf("%d", &t);
    for(int k = 1; k <= t; k++)
    {
        scanf("%d%s", &n, a + 1);
        for(int i = 1; i <= n; i++)
            s[i] = s[i - 1] + a[i] - '0'; //前缀和
        int l = 0;
        for(int i = 1; i <= n / 2 + 1; i++) //枚举起点,找最大值即可
            l = max(l, func(i));
        printf("Case #%d: %d\n",k, l);
            
    }
    return 0;
}

K倍区间


分析

要求区间和,我们使用前缀和,可得

(Sj - Si) mod K == 0

根据同余定理变形可得

Sj mod K == Si mod K

所以我们只需要枚举Sj,每次记录与他同余的Si数量便好。(mod == 0需要单独计算)


代码

// 暴力O(n ^ 2), 
// S % k == 0, 将S拆分——(Sj - Si)% k == 0 <---> Sj % k = Si & k转化成同余问题
// 而题目要求连续的区间,转换成后缀减去前缀,所以直接打表即可
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];
LL s[N];
int st[N];
int n, k;
int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d", a + i);
        s[i] = s[i - 1] + a[i];
    }
    LL l = 0;
    for(int i = 1; i <= n; i++)
    {
        int m = s[i] % k;
        if(m == 0) l++;
        l += st[m]++;
    }
    printf("%lld", l);
    return 0;
}

统计子矩阵


分析

直接枚举矩阵的话时间复杂度为O(n ^ 4),很显然会超时。

考虑优化,我们发现每个点的数值都是大于0的,即矩阵和会随矩阵的增大而增大,具有单调性

但是在二维上利用单调性显然是非常复杂的,如何简化问题呢?

降维!我们将枚举两个点拆分为上下边界和左右边界

我们先对每一列进行前缀和操作,随后每次先枚举上下边界。

问题转换为了区间求和问题,显然暴力枚举区间的话还是会超时,如何优化呢?

前面说了区间具有单调性,我们需要的只是满足条件的那一部分,那么有没有一种算法可以完美的避开不能满足要求的那一部分呢?

想到这里,四个大字从主播的脑海里腾空而起——滑动窗口


代码

 // 暴力的话O(n ^ 4)超时
 // 观察矩阵越大和越大,满足单调性,考虑双指针优化
#include<iostream>
using namespace std;
const int N = 510;
typedef long long LL;
int n, m, k;
int a[N][N], s[N][N];
LL cnt;
int main()
{
    scanf("%d%d%d", &n, &m, &k);
    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] + a[i][j] - s[i - 1][j - 1]; // 合并两个区间,容斥原理
        }
    for(int i = 1; i <= n; i++)
        for(int j = i; j <= n; j++)
        { //确定区间的上届和下届
            // 滑动窗口
            int l = 1, r = 0;
            while(++r <= m) 
            {
                while(l <= r && s[j][r] - s[j][l - 1] - s[i - 1][r] + s[i - 1][l - 1] > k)
                    l++;
                if(l <= r)  cnt += r - l + 1;
            }
        }
    cout << cnt;
    return 0;
}

递增三元组



分析

显然暴力枚举的话会超时,需要优化。

我们将不等式拆分:

  • Ai < Bj

  • Bj < Ck

先来算下面的不等式。

要计算满足Bj < Ck,的二元组的数量可以先将BC排序。

随后通过双指针计算出对于每一个Bj,满足的Cj的数量。

我们便得出了下面的不等式的数量,那么如何代入上面的不等式呢?

由于<的包含性,所以对于每个A,我们计算的是满足Bj < Cj二元组上的一个后缀

所以我们先对计算出来的二元组进行前缀和操作。

随后排序A,利用双指针计算即可。


代码

// 暴力的话O(n ^ 3),会超时,考虑优化
// 随后我们发现可以排序,通过双指针逐层计算。想不出来和前缀和有什么关联
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n;
int a[N], b[N], c[N];
int st[N];
LL s[N];
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", a + i);
    for(int i = 1; i <= n; i++)
        scanf("%d", b + i);
    for(int i = 1; i <= n; i++)
        scanf("%d", c + i);
    sort(a + 1, a + n + 1); sort(b + 1, b + n + 1); sort(c + 1, c + n + 1);
    for(int i = 1, j = 1; i <= n && j <= n; i++)
    {
        while(j <= n && c[j] <= b[i]) j++;
        st[i] = n - j + 1; //计算出b上每个点满足要求的数量
    } //前缀和
    
    for(int i = 1; i <= n; i++)
        s[i] = s[i - 1] + st[i];
    LL l = 0;
    for(int i = 1, j = 1; i <= n && j <= n; i++)
    {
        while(j <= n && b[j] <= a[i]) j++;
        l += s[n] - s[j - 1];
    }
    printf("%lld", l);
    return 0;
}

激光炮弹


分析

要计算矩阵和,使用二维前缀和。(这道题主要是考察细节,空间上比较接近上限,要严格判断每个数据会不会溢出)


代码

#include<iostream>
using namespace std;
typedef long long LL;
const int N = 5010;
int n, r;
int s[N][N], l;
int main()
{
    scanf("%d%d", &n, &r);
    for(int i = 0; i < n; i++)
    {
        int x, y, w;
        scanf("%d%d%d", &x, &y, &w);
        s[x + 1][y + 1] += w;
    }
    for(int i = 1; i < N; i++)
        for(int j = 1; j < N; j++)
            s[i][j] += s[i - 1][j] + s[i][j - 1] -s[i - 1][j - 1];
            
    if(r >= N)
    {
        printf("%lld", s[N - 1][N - 1]);
        return 0;
    }
    for(int i = 1; i + r - 1 < N; i++)
        for(int j = 1; j + r - 1 < N; j++) //枚举起点
            l = max(l, s[i + r - 1][j + r - 1] - s[i + r - 1][j - 1] - 
            s[i - 1][j + r - 1] + s[i - 1][j - 1]);
    printf("%lld", l);
    return 0;
}

总结

困QAQ。主播要休息了,明天是差分和双指针。

相关文章:

  • Xinference安装、使用详细笔记
  • 数据库原理13
  • tcl语法中的命令
  • </mirrorOf> Maven
  • 零基础入门网络爬虫第5天:Scrapy框架
  • 嵌入式驱动开发方向的基础强化学习计划
  • 【监控系列】ELK
  • 《水上安全》杂志社水上安全编辑部水上安全杂志2025年第3期目录
  • 持续集成与持续交付:这里有从开发到部署的全流程优化
  • Linux 基础入门操作 第十二章 TINY Web 服务器
  • MyBatis StatementHandler是如何创建 Statement 对象的? 如何执行 SQL 语句?
  • mac怎么安装pycharm?
  • 【加密社】币圈合约交易量监控,含TG推送
  • 简单描述一下,大型语言模型简史
  • 内网穿透的应用-本地部署ChatTTS教程:Windows搭建AI语音合成服务的全流程配置
  • JavaScript数组和对象
  • DeepSeek面试——模型架构和主要创新点
  • C# SerialPort 类中清空缓存区的方法
  • AI对软件工程(software engineering)的影响在哪些方面?
  • JVM常用垃圾回收器
  • 解放日报:持续拿出排头兵姿态先行者担当
  • “光荣之城”2025上海红色文化季启动,红色主题市集亮相
  • “即买即退”扩容提质,上海静安推出离境退税2.0版新政
  • 阿里开源首个“混合推理模型”:集成“快思考”、“慢思考”能力
  • 人到中年为何腰围变粗?科学家发现腹部脂肪增加的细胞元凶
  • 特朗普声称中方领导人打了电话,外交部:近期中美元首没有通话