



第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;
}
这是一道综合能力题,考察了多个知识点的结合:
-
发现规律的能力:通过不断尝试拆分,观察出"优先拆 3"的数学规律。
-
动态规划思想:把"大问题"拆成"+2""+3"两种状态转移。
-
预处理思想:一次计算全部答案,后续查询直接输出。
-
对数优化 :利用
ln(a×b)=ln(a)+ln(b)将无法直接比较的大整数乘积转化为对数和进行比较。 -
取模思想:比较大小和输出答案分别维护,避免大整数溢出。
-
状态设计能力:一个数组负责"比较",一个数组负责"输出",各司其职。