记录103
cpp
#include<bits/stdc++.h>
using namespace std;//多重背包转01
int w[7]={0,1,2,3,5,10,20};//砝码重
int cnt[7];//各类砝码
bool dp[1010];//每钟重量的情况
int main(){
for(int i=1;i<=6;i++) cin>>cnt[i];
int max_w=0;//最大重量
for(int i=1;i<=6;i++) max_w+=w[i]*cnt[i];//所有砝码的总重
dp[0]=1;//初始化0重量
int weight=0,num=0;//记录每次重量,数量
for(int i=1;i<=6;i++){//每个重量的砝码拿一遍,当作01背包
weight=w[i];//记录重量
num=cnt[i];//记录数量
for(int j=1;j<=num;j++){//对应砝码数量
for(int k=max_w;k>=weight;k--){//各重量情况
if(dp[k-weight]) dp[k]=1;//是否联通
}
}
}
int ans=0;//每种总量的总情况
for(int i=1;i<=max_w;i++){
if(dp[i]) ans++;//累加方案数
}
cout<<"Total="<<ans;
return 0;
}
题目传送门
https://www.luogu.com.cn/problem/P2347
突破口
设有 1g、2g、3g、5g、10g、20g 的砝码各若干枚(其总重 ≤1000),可以表示成多少种重量?
思路
🔍 一、题目核心理解
🎯 问题描述
- 有 6 种砝码:1g、2g、3g、5g、10g、20g
- 每种砝码有若干个(输入
a1 ~ a6) - 总重量 ≤ 1000
- 问:能称出多少种不同的正整数重量?(不能一个都不用)
✅ 注意:砝码只能放在一边(题目未提天平两边,是"表示成多少种重量",即组合求和)
→ 这是一个典型的 多重背包可行性问题:
- 物品:每种砝码
- 数量:
a_i个 - 重量:
w_i - 目标:求所有可能的 非零总重量 的种类数
📌 举例(样例)
输入:1 1 0 0 0 0
- 1g 砝码 1 个,2g 砝码 1 个
- 可能组合:
- 1g → 1
- 2g → 2
- 1g+2g → 3
- 共 3 种 → 输出
Total=3
🧠 二、解题思路:多重背包转 0-1 背包(暴力拆分)
方法选择
- 数据规模小(总重 ≤ 1000,最多 1000 个砝码)
- 可直接将每种砝码拆成多个单个物品(即 0-1 背包)
- 用布尔数组
dp[w]表示重量w是否可达
状态定义
dp[w] = true表示可以称出重量w
初始化
dp[0] = true(不放任何砝码,重量为 0)
转移
对每种砝码 i(重量 w[i],数量 cnt[i]):
- 拆成
cnt[i]个独立物品 - 对每个物品,做 0-1 背包更新(倒序)
⚠️ 虽然效率不是最优(可用二进制优化或单调队列),但本题数据小,完全可行
最终答案
- 统计
dp[1]到dp[max_w]中true的个数
代码分析
cpp
#include<bits/stdc++.h>
using namespace std;
int w[7] = {0, 1, 2, 3, 5, 10, 20}; // 砝码重量,下标1~6对应6种
int cnt[7]; // cnt[i]:第i种砝码的数量
bool dp[1010]; // dp[w]:能否称出重量w(总重≤1000)
w[0]=0是占位符,实际使用w[1]~w[6]dp大小1010:因为总重 ≤1000,安全
cpp
int main(){
for(int i = 1; i <= 6; i++)
cin >> cnt[i]; // 读入每种砝码的数量
cpp
int max_w = 0;
for(int i = 1; i <= 6; i++)
max_w += w[i] * cnt[i]; // 计算所有砝码的总重量(最大可能称重)
max_w是理论最大值,用于限制 DP 范围
cpp
dp[0] = 1; // 初始状态:重量0总是可达(不放砝码)
cpp
int weight = 0, num = 0;
for(int i = 1; i <= 6; i++){ // 枚举每种砝码
weight = w[i]; // 当前砝码重量
num = cnt[i]; // 当前砝码数量
for(int j = 1; j <= num; j++){ // 拆成 num 个独立物品
for(int k = max_w; k >= weight; k--){ // 0-1背包倒序更新
if(dp[k - weight])
dp[k] = 1; // 如果 k-weight 可达,则 k 也可达
}
}
}
🔄 三层循环详解:
- 外层
i:处理第i种砝码 - 中层
j:将cnt[i]个砝码逐个拆开(暴力转 0-1 背包) - 内层
k(倒序) :- 从
max_w到weight - 若
dp[k - weight] == true,说明加上当前砝码可得k - 标记
dp[k] = true
- 从
✅ 这是标准的 多重背包暴力拆分 + 0-1 背包 实现
cpp
int ans = 0;
for(int i = 1; i <= max_w; i++){
if(dp[i]) ans++; // 统计所有正重量的可达情况
}
- 从 1 开始(排除 0 重量)
- 累加可达的重量种类数
cpp
cout << "Total=" << ans;
return 0;
}
- 按题目要求格式输出
⚠️ 关键细节说明
| 细节 | 说明 |
|---|---|
| 为什么倒序? | 防止同一个砝码被多次使用(虽然是拆开的,但若正序,同一轮中会重复累加) |
dp[0]=1 的作用? |
提供基础状态,使得第一个砝码可以被选(如 dp[1] = dp[0] + 1g) |
| 是否包含 0? | 不包含!最后统计从 i=1 开始 |
| 效率如何? | 最坏情况:总砝码数 ≤1000,max_w ≤1000 → 循环约 1000×1000 = 1e6,完全可行 |
🧪 样例验证
输入:1 1 0 0 0 0
cnt[1]=1(1g),cnt[2]=1(2g)max_w = 1×1 + 1×2 = 3- DP 过程:
- 处理 1g:
dp[1] = true - 处理 2g:
dp[2]=true,dp[3]=true(因dp[1]已 true)
- 处理 1g:
dp[1], dp[2], dp[3]为 true →ans=3✅
✅ 总结:问题与解法对应
表格
| 题目要求 | DP 设计 |
|---|---|
| 多种砝码,有限数量 | 多重背包 |
| 求能组成的不同重量数 | 可行性 DP(bool dp[]) |
| 不包括 0 | 统计从 1 开始 |
| 输出格式固定 | "Total=N" |