MT2048三连 暴力→数学推导→O (n) 优化

题目:

给出一个长度为n的序列,问有多少种方案将序列划分为恰好连续的三段(每个元素都属于某一段),使得每一段的和都相等。

格式

输入格式:

第一行:一个整数n;

第二行:n个整数,表示序列。

输出格式:

一个整数表示方案数。

样例 1

输入:

复制代码
4
1 2 3 3

复制

输出:

复制代码
1

TLE代码:双层循环

cpp 复制代码
#include<bits/stdc++.h> 

using namespace std;
long long sum[100005];
    long long a[100005];
int main( )
{
    long long n;
    cin>>n;
    sum[0]=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        sum[i]=sum[i-1]+a[i];
    }
   
    long long ans=0;
    for(int i=1;i<=n-2;i++){
        for(int j=i+1;j<=n-1;j++){
                if((sum[i]==sum[j]-sum[i])&&(sum[i]==sum[n]-sum[j]))
                ans++;
            
        }
       
    }
    cout<<ans;
    return 0;
}

优化:

s1=sum[i],s2=sum[j]-sum[i] s3 =sum[n]-sum[j]

1.第一个浪费点------既然 s1=s2,那 sum [j] 等于什么?

sum[j] 是前 j 个元素的和,也就是第一段加第二段,即sum[j] = s1 + s2

现在你的 if 里已经要求 s1 == s2 了,那我们把 s2 换成 s1,代入上面的式子:

sum[j] = s1 +s2= 2 * s1

2.第二个浪费点------那 s1 又必须等于多少呢?

我们再看整个数组的总和 total

total = s1 + s2 + s3

你的 if 要求 s1 = s2 = s3,那总和就是:

total = s1 + s1 + s1 = 3 *s1

反过来算,s1 就必须等于:

s1 = total / 3

我们给这个固定值起个名字,就叫 target(目标值)。target=total / 3

3.把前两步的结论拼回去 ------ if 条件完全变样了

cpp 复制代码
if (sum[i] == target && sum[j] == 2*target)

4.数有多少对 (i,j)

举个例子

total = 6,所以 target = 2,2*target = 4

我们算出来的 sum 数组是:[0, 1, 2, 3, 4, 5, 6]

我把这个 sum 数组写成一排,像这样:

cpp 复制代码
下标: 0   1   2   3   4   5   6
sum: [0] [1] [2] [3] [4] [5] [6]
              ↑       ↑
            target  2*target

现在我问你:要数有多少对 (i,j) 满足 i<j,sum [i]=2,sum [j]=4,你会怎么数?

你肯定会这样数(这是人的本能,根本不是算法):

  1. 你从左往右走;

  2. 走到下标 2,看到 sum[2]=2,你就在心里记一笔:"现在我遇到了 1 个 target";

  3. 继续走到下标 4,看到 sum[4]=4,你就想:"刚才我记了 1 个 target,那这里就有 1 种配对方案",然后把答案加 1;

  4. 数完了。

你看! 你根本不会用两层循环去 "枚举所有 i 和 j",你只会 **"一边走,一边记,遇到目标就加"**!

cpp 复制代码
long long cnt = 0; // 记一下遇到了多少个 target
long long ans = 0;

for (int j = 1; j <= n-1; j++) {
    // 先看:现在是不是 2*target?如果是,答案加上之前记的 cnt
    if (sum[j] == 2 * target) {
        ans += cnt;
    }
    // 再看:现在是不是 target?如果是,记下来 cnt++
    if (sum[j] == target) {
        cnt++;
    }
}

总结:

1.把你的 if 条件抄在草稿纸上;

  1. 用简单的代数移项,把条件翻译成「sum [j] 等于什么」、「sum [i] 等于什么」;

  2. 代入一个超级简单的小例子,用手「从左到右数一遍」,看看你自己是怎么数的。

积累反射:

题目信号 条件反射想到的方法 原因
求「连续子段和」 前缀和数组 前缀和就是专门用来快速算连续子段和的
数据规模 n≤1e5 必须用 O(n) 或 O(nlogn) 的算法 O(n2) 的算法在 n=1e5 时,运算次数是 1e10,电脑 1 秒只能跑约 1e8 次,肯定超时
求「满足条件的配对数 (i,j)」且 i<j 一边遍历一边计数,不用枚举所有配对 只要统计「前面出现过多少次目标值」,遇到当前值时直接加次数即可
「分成 k 段,每段和相等」 先算总和,判断是否能被 k 整除 总和必须是 k 的倍数,否则直接无解,这是数学上的必要条件

数学直觉的培养

------ 先写公式,再写代码

盯着你的暴力代码问:

  1. 「哪一步计算是重复的?」(比如重复算子段和→前缀和)

  2. 「哪一层循环是多余的?」(比如枚举所有 i→只需要计数)

  3. 「有没有数学上的必要条件,可以先排除无解的情况?」(比如总和 %3==0)

  4. 第三阶段:「看题解,补思维漏洞」

如果你实在想不出优化方法,就去看题解,但不要只抄代码,要问自己:

  1. 「题解的哪一步是我没想到的?」

  2. 「我为什么没想到?是因为不知道某个数据结构,还是因为没发现某个数学规律?」

  3. 「把这个题的『信号 - 方法』对应关系,记到我的笔记本上。」

  4. 第四阶段:「换个问法,举一反三」

做完这个题,你可以自己给自己出题:

  1. 「如果是分成 4 段,每段和相等,怎么做?」

  2. 「如果是求『有多少个连续子段和等于 target』,怎么做?」(这就是经典的「前缀和 + 哈希表」题)

  3. 「如果数组里有负数,这个方法还能用吗?」(当然能用,前缀和天然支持负数

正确代码:

cpp 复制代码
#include<bits/stdc++.h> 

using namespace std;

int main( )
{
    long long n;
    cin>>n;
    long long a[n],sum=0;
    for(int i=0;i<n;i++){
        cin>>a[i];
        sum+=a[i];
    }
    if(sum%3!=0){
        cout<<0;
        return 0;
    }
    long long count=0,sum0=0;
    long long target=sum/3;
    long long ans=0;
    for(int i=0;i<n-1;i++){
        sum0+=a[i];
         if(sum0==target*2) ans+=count;
        if(sum0==target) count++;
       
    }
    cout<<ans;
    return 0;
}
相关推荐
ximu_polaris1 小时前
设计模式(C++)-行为型模式-模版方法模式
c++·设计模式
码之气三段.1 小时前
十五届山东ccpc省赛补题(update)
数据结构·c++·算法
AI科技星2 小时前
ELN 升级:π 级数自动生成器全域数理架构
大数据·人工智能·python·算法·金融
强盛机器学习~2 小时前
2026年SCI一区新算法-傅里叶变换优化算法(FTO)-公式原理详解与性能测评 Matlab代码免费获取
算法·matlab·进化计算·群体智能·傅里叶变换·元启发式算法
王老师青少年编程2 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【跳跃与过河问题】:过河问题
c++·算法·贪心·csp·信奥赛·跳跃与过河问题·过河问题
是个西兰花2 小时前
C++11:智能指针
开发语言·c++·智能指针·rall
CN-Dust2 小时前
【C++专题】输出cout例题
开发语言·c++
沉默-_-2 小时前
备战蓝桥杯-哈希
c++·学习·算法·蓝桥杯·哈希算法
Reese_Cool2 小时前
【STL】蓝桥杯/天梯赛终极杀器!10个C++字符串核心技巧,暴力破解高频考点
开发语言·c++·蓝桥杯·stl