记录87
cpp
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int dp[N];
int main(){
int n;
cin>>n;
dp[0]=1;// 开始的第一个方案,以它为迭代
for(int i=4;i<=n;i+=4) dp[i]+=dp[i-4];
for(int i=5;i<=n;i++) dp[i]+=dp[i-5];
cout<<dp[n];
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P8395
突破口
14=5+5+4
20=4+4+4+4+4 或 20=5+5+5+5
40=4+4+4+4+4+4+4+4+4+4 或 40=4+4+4+4+4+5+5+5+5 或 40=5+5+5+5+5+5+5+5
当然,4 和 5 的顺序并不重要,重要的是他们的个数。
给你一个正整数 n,问有多少种方法可以用 4 和 5 拼凑成 n。
思路
🎯 问题本质
给定一个正整数 n,问有多少种非负整数解 (x, y) 满足:
4x+5y=n4x+5y=n
其中 x 是 4 的个数,y 是 5 的个数。
顺序不重要 → 只关心用了多少个 4 和多少个 5,不关心排列顺序。
✅ 这是一个完全背包计数问题 :
物品:4 和 5(每种无限个)
背包容量:n
问:恰好装满背包的方案数?
🧠 动态规划设计(完全背包)
状态定义
dp[i]表示用 4 和 5 恰好拼出 i 的方案数
初始状态
dp[0] = 1:拼出 0 有一种方案 ------ 什么都不选
状态转移
- 先考虑只用 4 :
dp[i] += dp[i - 4](如果 i ≥ 4) - 再考虑加入 5 :
dp[i] += dp[i - 5](如果 i ≥ 5)
⚠️ 注意:必须按"物品"顺序处理 ,避免重复计数!
正确做法是:外层枚举物品,内层枚举容量(完全背包标准写法)
但本题只有两个物品(4 和 5),所以可以分两步:
- 先用 4 更新所有能被 4 拼出的数
- 再用 5 去更新(在已有基础上加 5)
这样就能保证:每个方案只按"先 4 后 5"的方式计数一次,不会重复。
代码解析
cpp
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int dp[N];
- 定义
dp数组,大小1e6+10(满足 n ≤ 1e6)
cpp
int main(){
int n;
cin >> n;
cpp
dp[0] = 1; // 基础:拼出 0 有 1 种方案(空方案)
🔹 第一步:只用数字 4
cpp
for(int i = 4; i <= n; i += 4)
dp[i] += dp[i - 4];
- 从 4 开始,每次加 4
dp[4] += dp[0] = 1→dp[4] = 1dp[8] += dp[4] = 1→dp[8] = 1- ...
- 结果:所有
i % 4 == 0的位置dp[i] = 1,其余仍为 0
✅ 这表示:仅用 4 能拼出的数,每种只有 1 种方案
🔹 第二步:加入数字 5(完全背包)
cpp
for(int i = 5; i <= n; i++)
dp[i] += dp[i - 5];
- 从 5 到 n,顺序遍历
- 对每个
i,加上"去掉一个 5 后的方案数" - 因为是顺序遍历,所以允许多次使用 5(完全背包特性)
💡 举例:
i=9:dp[9] += dp[4] = 1→ 表示4+5i=13:dp[13] += dp[8] = 1→4×2 + 5i=14:dp[14] += dp[9] = 1→4 + 5×2→ 总方案数 1 ✅
🔚 输出答案
cpp
cout << dp[n];
return 0;
}
🧪 样例验证
样例 1:n = 14
- 4 的倍数:4,8,12 →
dp[4]=dp[8]=dp[12]=1 - 加入 5:
dp[9] = dp[4] = 1dp[13] = dp[8] = 1dp[14] = dp[9] = 1
- 其他组合?
5×2 + 4 = 14→ 只有这一种 - 输出
1✅
样例 2:n = 40
可能的 (x, y) 满足 4x + 5y = 40:
- y=0 → x=10
- y=4 → x=5 (5×4=20, 4×5=20)
- y=8 → x=0
→ 共 3 种 ✅
代码计算:
dp[40]会累加:- 仅用 4:1 种
- 用 4 个 5:
dp[20](此时dp[20]已包含4×5和5×4?)
等等,我们验证 dp[20]:
- 仅用 4:
dp[20] = 1 - 加入 5 后:
dp[20] += dp[15]dp[15] += dp[10]dp[10] += dp[5]dp[5] += dp[0] = 1- 所以
dp[5]=1,dp[10]=1,dp[15]=1,dp[20]=1+1=2
- 那么
dp[40]:- 初始(仅4):1
dp[40] += dp[35]dp[35] += dp[30]- ... 最终
dp[40] = 3✅
样例 3:n = 6
- 无法用 4 和 5 拼出 6(4<6<8,5<6<10,且 4+5=9>6)
dp[6]始终为 0 → 输出0✅
⚠️ 为什么这个顺序是对的?
- 先处理 4:确保所有"纯 4"方案被记录
- 再处理 5:在已有方案(含 4)基础上,添加任意多个 5
- 由于 5 是从小到大顺序遍历 ,所以:
dp[i]包含了使用 0 个、1 个、2 个......5 的所有方案
- 而且因为 4 和 5 的处理是分阶段 的,不会出现
(4,5)和(5,4)被算两次(因为顺序不重要,本题只计组合,不计排列)
总结
| 要点 | 说明 |
|---|---|
| 模型 | 完全背包计数问题 |
| 状态 | dp[i] = 拼出 i 的方案数 |
| 初始化 | dp[0] = 1 |
| 转移 | 先用 4 更新,再用 5 顺序更新 |
| 复杂度 | O(n),满足 n ≤ 1e6 |