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"
相关推荐
WolfGang0073211 天前
代码随想录算法训练营 Day38 | 动态规划 part11
算法·动态规划
松☆1 天前
C++ 算法竞赛题解:P13569 [CCPC 2024 重庆站] osu!mania —— 浮点数精度陷阱与 `eps` 的深度解析
开发语言·c++·算法
(Charon)1 天前
【C++/Qt】C++/Qt 实现 TCP Server:支持启动监听、消息收发、日志保存
c++·qt·tcp/ip
jr-create(•̀⌄•́)1 天前
正则化和优化算法区别
pytorch·深度学习·神经网络·算法
并不喜欢吃鱼1 天前
从零开始C++----七.继承及相关模型和底层(上篇)
开发语言·c++
li星野1 天前
刷题:数组
数据结构·算法
tankeven1 天前
HJ182 画展布置
c++·算法
W23035765731 天前
【改进版】C++ 固定线程池实现:基于调用者运行的拒绝策略优化
开发语言·c++·线程池
谭欣辰1 天前
C++ 控制台跑酷小游戏
c++·游戏