P2347 [NOIP 1996 提高组] 砝码称重

记录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 也可达
            }
        }
    }

🔄 三层循环详解:

  1. 外层 i :处理第 i 种砝码
  2. 中层 j :将 cnt[i] 个砝码逐个拆开(暴力转 0-1 背包)
  3. 内层 k(倒序)
    • max_wweight
    • 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)
  • dp[1], dp[2], dp[3] 为 true → ans=3

✅ 总结:问题与解法对应

表格

题目要求 DP 设计
多种砝码,有限数量 多重背包
求能组成的不同重量数 可行性 DP(bool dp[]
不包括 0 统计从 1 开始
输出格式固定 "Total=N"
相关推荐
luj_17682 小时前
残熵算法实时化三大瓶颈突破
c语言·开发语言·网络·经验分享·算法
mCell3 小时前
你以为短链接只是 Hash + 301/302?
后端·算法·架构
远离UE44 小时前
UE5 compute shader 原子加
开发语言·c++·ue5
C+-C资深大佬4 小时前
C++ 显式类型转换详解:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
码少女5 小时前
数据结构——哈希表的基础
数据结构·散列表
KaMeidebaby5 小时前
卡梅德生物技术快报|抗体亲和力成熟工业化调控新机制:差异性浆细胞增殖工艺优化思路
java·开发语言·人工智能·算法·机器学习·架构·spark
luj_17685 小时前
心形曲线轨迹控制三大关键技术
c语言·开发语言·c++·经验分享·算法
取地址符5 小时前
C++学习笔记(基于learn-cxx)(1)
c++·经验分享·笔记·学习
tmlx3I0816 小时前
高光谱拼接算法(六)RANSAC 误匹配剔除
人工智能·算法·机器学习
不相心 -w-6 小时前
基础-滑动窗口-(定长 | 不定长)
算法