GESP2026年3月认证C++七级( 第三部分编程题(1、拆分))精讲



第1课时:整数拆分------从暴力到数学规律

目标:

不急着讲代码,让学生自己发现规律。


第一幕:国王的金币

故事导入:

国王说:

我有一个数字 N。

你必须把它拆成几个正整数。

例如:

复制代码
5

可以拆成

复制代码
1+4

2+3

1+1+3

1+2+2

1+1+1+2

......

每一种拆法,

都会得到一个乘积。

例如

复制代码
2+3

乘积

=6

谁最大?


第二幕:先暴力枚举

例如

复制代码
N=5

所有拆法:

复制代码
4+1

3+2

3+1+1

2+2+1

2+1+1+1

1+1+1+1+1

计算乘积:

复制代码
4

6

3

4

2

1

最大的:

复制代码
6

继续:

复制代码
N=6

大家一起算:

复制代码
5+1=5

4+2=8

3+3=9

2+2+2=8

......

最大:

复制代码
9

再算:

复制代码
7

得到:

复制代码
12

一直做到

复制代码
10

整理成表格

n 最大乘积
2 2
3 3
4 4
5 6
6 9
7 12
8 18
9 27
10 36

学生开始发现:

规律越来越明显。


第三幕:为什么不要拆出1?

举例:

复制代码
5

=

4+1

乘积:

复制代码
4

如果改成:

复制代码
2+3

乘积:

复制代码
6

更大。

再举:

复制代码
8

=

7+1

=7

改成:

复制代码
5+3

15

更大。

于是得到第一个规律:

除了特殊情况,不应该出现1。


第四幕:为什么不要出现5?

例如:

复制代码
5

保持:

复制代码
5

乘积:

复制代码
5

拆成

复制代码
2+3

乘积:

复制代码
6

变大。

所以

复制代码
5

↓

2+3

永远更优。


继续:

复制代码
6

拆?

复制代码
3+3

9

复制代码
6

大。


复制代码
7

↓

3+4

12

复制代码
8

↓

3+5

15

↓

3+2+3

18

越来越发现:


第五幕:为什么3最神奇?

课堂实验:

复制代码
12

方法一:

复制代码
6+6

36

方法二:

复制代码
4+4+4

64

方法三:

复制代码
3+3+3+3

81

最大!

继续:

复制代码
15

比较:

复制代码
5+5+5

125

3+3+3+3+3

243

明显3最好。

最后总结:

能拆3,

就优先拆3。


第六幕:剩1怎么办?

例如:

复制代码
10

=

3+3+3+1

乘积:

复制代码
27

不好。

怎么办?

最后两个:

复制代码
3+1

↓

2+2

于是

复制代码
3+3+2+2

36

更大。

得到最终规律:

复制代码
余0

全拆3

余1

留4

拆2+2

余2

最后留2

讲到这里,

学生应该已经知道:

为什么答案总和3有关。


第二课时:为何这道题用DP?

这一课真正进入七级内容。

因为:

复制代码
N≤10^6

数据范围很大,且结果需要对 (10^9) 取模。


第一幕:为什么不能直接算?

例如:

复制代码
3^300000

会发生:

复制代码
long long

爆了。

怎么办?

我们用了一个非常巧妙的方法。


第二幕:比较乘积,不比较乘积本身

例如:

复制代码
100000000000000000000

复制代码
200000000000000000000

谁大?

不用真的算。

因为

复制代码
log(A)>log(B)

⇔

A>B

所以:

乘法

变成

加法。

这里可以结合公式理解:

于是

复制代码
乘积

↓

对数和

第三幕:DP状态

定义:

复制代码
f[i]

答案(mod)

lnf[i]

真正大小

什么意思?

例如:

复制代码
f[8]

=18

但是

复制代码
lnf[8]

=

ln18

仅用于比较大小。


第四幕:状态转移

最后一步只有两种:

复制代码
+2

或者

复制代码
+3

于是:

复制代码
i

↓

i+2

乘积:

复制代码
×2

i

↓

i+3

乘积:

复制代码
×3

因此

复制代码
ln

+

ln2

或者

复制代码
+

ln3

代码只用两条转移就可以了。


比较三种写法。


方法一

数学规律:

复制代码
全部拆3

时间:

复制代码
O(logN)

(快速幂)


参考程序:

cpp 复制代码
#include <iostream>
using namespace std;

const long long MOD = 1000000000LL;

long long qpow(long long a, long long b)
{
    long long ans = 1;

    while (b)
    {
        if (b & 1)
            ans = ans * a % MOD;

        a = a * a % MOD;

        b >>= 1;
    }

    return ans;
}

int main()
{
    int T;
    cin >> T;

    while (T--)
    {
        long long n;
        cin >> n;

        if (n <= 4)
        {
            cout << n << endl;
            continue;
        }

        if (n % 3 == 0)
        {
            cout << qpow(3, n / 3) << endl;
        }
        else if (n % 3 == 1)
        {
            cout << qpow(3, n / 3 - 1) * 4 % MOD << endl;
        }
        else
        {
            cout << qpow(3, n / 3) * 2 % MOD << endl;
        }
    }
}

方法二

官方DP

复制代码
O(N)

预处理一次。

查询:

复制代码
O(1)

参考程序:

cpp 复制代码
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int N = 1e6 + 5;
const int mod = 1e9;
const double ln2 = log(2);
const double ln3 = log(3);
int f[N];
double lnf[N];
int main() {
    f[0] = f[1] = 1;
    for (int i = 0; i < N; i++) {
        if (i + 2 < N && lnf[i] + ln2 > lnf[i + 2]) {
            lnf[i + 2] = lnf[i] + ln2;
            f[i + 2] = 2ll * f[i] % mod;
        }
        if (i + 3 < N && lnf[i] + ln3 > lnf[i + 3]) {
            lnf[i + 3] = lnf[i] + ln3;
            f[i + 3] = 3ll * f[i] % mod;
        }
    }
    int t;
    scanf("%d", &t);
    while (t--) {
        int n;
        scanf("%d", &n);
        printf("%d\n", f[n]);
    }
    return 0;
}

方法三

普通DP(无法满分)

状态:

复制代码
dp[i]

最大乘积

枚举拆点:

复制代码
j

i-j

复杂度:

复制代码
O(N²)

参考程序:

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    int n;
    cin >> n;

    long long dp[1005]={0};

    dp[1]=1;
    dp[2]=2;
    dp[3]=3;

    for(int i=4;i<=n;i++)
    {
        for(int j=1;j<i;j++)
        {
            dp[i]=max(dp[i],
                      max(
                          max(1LL*j*(i-j),
                              dp[j]*(i-j)),
                          max(1LL*j*dp[i-j],
                              dp[j]*dp[i-j])
                         )
                     );
        }
    }

    cout<<dp[n];

    return 0;
}

这是一道综合能力题,考察了多个知识点的结合:

  1. 发现规律的能力:通过不断尝试拆分,观察出"优先拆 3"的数学规律。

  2. 动态规划思想:把"大问题"拆成"+2""+3"两种状态转移。

  3. 预处理思想:一次计算全部答案,后续查询直接输出。

  4. 对数优化 :利用 ln(a×b)=ln(a)+ln(b) 将无法直接比较的大整数乘积转化为对数和进行比较。

  5. 取模思想:比较大小和输出答案分别维护,避免大整数溢出。

  6. 状态设计能力:一个数组负责"比较",一个数组负责"输出",各司其职。


相关推荐
一只旭宝17 小时前
C++手写shared_ptr共享智能指针|原子引用计数、强弱引用控制块、赋值重载底层深度剖析
开发语言·c++·面试
开发者联盟league18 小时前
Java 通过 JNA 调用 C++ DLL:关键流程总结
java·c++·jna
库克克19 小时前
【C++】智能指针
java·开发语言·c++
一拳一个呆瓜21 小时前
【STL】iostream 编程:输入流成员函数
c++·stl
xiaoye-duck21 小时前
《Linux系统编程》Linux 系统多线程(七): C++ 线程安全日志系统封装:基于策略模式解耦,兼容 glog 使用风格
linux·c++·日志系统
程序喵大人1 天前
【C++进阶】STL容器与迭代器 - 07 迭代器是容器和算法之间的通用游标
开发语言·c++·算法
BerryS3N1 天前
C++在AI时代的核心力量:从底层加速到大规模大模型推理引擎的高能实践
开发语言·c++·人工智能
FlightYe1 天前
音视频修炼之视频基础(一):视频基础理论
android·c++·vscode·音视频·androidx·android runtime
小小帅呀1 天前
CUDA编程实战09:CUDA Graph 实战——降低重复 Kernel 与流水线提交开销
c++·人工智能·矩阵