P8395 [CCC 2022 S1] Good Fours and Good Fives

记录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),所以可以分两步:

  1. 先用 4 更新所有能被 4 拼出的数
  2. 再用 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] = 1dp[4] = 1
  • dp[8] += dp[4] = 1dp[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+5
  • i=13: dp[13] += dp[8] = 14×2 + 5
  • i=14: dp[14] += dp[9] = 14 + 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] = 1
    • dp[13] = dp[8] = 1
    • dp[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×55×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
相关推荐
菜择贰6 小时前
B树的性质和查找、插入、删除操作
数据结构·b树
LDR0066 小时前
接口焦虑终结者:LDR6020 芯片如何重新定义 Type-C 拓展坞与多设备互联时代
数据结构·经验分享·智能音箱
房开民8 小时前
可变参数模板
java·开发语言·算法
t***5448 小时前
如何在现代C++中更有效地应用这些模式
java·开发语言·c++
_深海凉_8 小时前
LeetCode热题100-最小栈
java·数据结构·leetcode
不知名的忻8 小时前
Morris遍历(力扣第99题)
java·算法·leetcode·morris遍历
状元岐8 小时前
C#反射从入门到精通
java·javascript·算法
itman3018 小时前
C语言、C++与C#深度研究:从底层到现代开发演进全解析
c语言·c++·c·内存管理·编译模型
_深海凉_9 小时前
LeetCode热题100-除了自身以外数组的乘积
数据结构·算法·leetcode
Kk.08029 小时前
项目《基于Linux下的mybash命令解释器》(一)
前端·javascript·算法