5.12回溯法--连续邮资问题--子集树

news2024/12/23 23:38:19

回溯法的题目太多了,不想写这个代码了,于是我就开始水一篇文章,就单纯的分析一下这个问题保持整本书完整的队形

问题描述

如何用有限的邮票数,贴出更多面额的需求?

 

举例

n=5,m=4

设计1:X1={1, 3, 11, 15, 32}            

连续邮资区间{1, 2, 3, ……, 70}

设计2:X2={1, 6, 10, 20, 30}              

邮资连续区间{1, 2, 3, 4}

问题分析

 定义x[1:n]表示n种不同邮票的面值,并约定他们从小到大排列,x[1]=1是唯一的选择,因为最大邮资区间必须从1开始。

接下来x[2]的取值范围是[2:m+1],因为第一张是1,最多可以贴m张

于是可以推测:对于已经选定的x[1:i-1],最大的连续邮资区间是[1:r],那么接下来x[i]的取值范围是[x_{i-1}+1 : r+1],若Xi>r+1, 则邮资r+1无法支付(约束条件)

在用回溯法解题目时,解空间树中各个节点的儿子是动态变化的,随着x的取值不同而变化

r 怎么计算呢? yi(j) 表示最多用 m 张面值 xi 的邮票贴出邮资 j 所需要的最少邮票张数

计算X[1:i]的最大连续邮资区间在本算法中被频繁使用到,因此势必要找到一个高效的方法。

考虑到直接递归的求解复杂度太高,我们不妨尝试计算用不超过m张面值为x[1:i]的邮票贴出邮资k所需的最少邮票数y[k]。通过y[k]可以很快推出r的值。

//x[i-2]*(m-1)是第i-2层循环的一个上限,目的是找到r-1的值 
for (int j=0; j<= x[i-2]*(m-1);j++)         
    if (y[j]<m)          
        //k是对表示j剩余的票数进行检查   
        for (int k=1;k<=m-y[j];k++)    
                //x[i-1]*k是k张邮票能表示的最大邮资   
                //+j表示增加了i邮资后能   
                //判断新增加的能表示的邮资需要多少         
        if (y[j]+k<y[j+x[i-1]*k]) 
            y[j+x[i-1]*k]=y[j]+k; //对第i-2层扩展一个x[i-1]后的邮资分布
 //向后寻找最大邮资值,查看邮资范围扩大多少,然后查询y数组从而找到r 
while (y[r]<maxint) 
    r++;  //计算X[1:i]的最大连续邮资区间

回溯函数

 

 定义一些必要的变量

 伪代码

 代码(我抄的)

#include <stdio.h>
#define maxl 1000 //表示最大连续值
#define maxint 32767
int n, m;       // n为邮票种类数,m为能贴的最大张数
int maxvalue;   //表示最大连续值
int bestx[100]; //表示最优解
int y[maxl];    // y[k],存储表示到k值,所使用的最少邮票数
int x[100];     //存储当前解
void backtrace(int i, int r);

int main(){
    printf("请输入邮票面值数:");
    scanf("%d", &n);
    printf("请输入能张贴邮票的最大张数:");
    scanf("%d", &m);
    for (int i = 0; i <= n; i++){
        x[i] = 0;
        bestx[i] = 0;
    }
    for (int i = 0; i < maxl; i++)  y[i] = maxint;
    x[1] = 1;
    y[0] = 0;
    maxvalue = 0;
    backtrace(1, 0);
    printf("当前最优解为:");
    for (int i = 1; i <= n; i++)  printf("%d ", bestx[i]);
    printf("\n最大连续邮资为:");
    printf("%d", maxvalue);
    return 1;
}

void backtrace(int i, int r){
    //对上一层的邮资值数组进行更新,上限是x[i-1]*m
    for (int j = 0; j <= x[i - 1] * m; j++){
        if (y[j] < m){
            //从只使用一个x[i]到使用m-y[i]个,即使用最多的最大值,降低邮票数
            //k是对表示j剩余的票数进行检查  
            for (int k = 1; k <= m - y[j]; k++){
                if (y[j] + k < y[j + x[i] * k]){
                //如果前面的某一个情况加上k个x[i],所达到邮资值使用的邮票数少于原来的邮票数则更新
                    y[j + x[i] * k] = y[j] + k;
                }
            }
        }
    }
    //向后寻找最大邮资值,查看邮资范围扩大多少,然后查询y数组从而找到r 
    while (y[r] < maxint){
        r++; //计算X[1:i]的最大连续邮资区间
    }
    if (i == n){ // i=n表示到达叶子节点。
        if (r - 1 > maxvalue){ //若大于最大值,则更新最优值与最优解
            for (int k = 1; k <= n; k++){
                bestx[k] = x[k];
            }
            maxvalue = r - 1;
        }
        return;
    }
    int z[maxl];
    //由于每一层需要对多种情况进行运算,因此需要将上一层的邮资值数组保留
    for (int k = 0; k < maxl; k++){
        z[k] = y[k];
    }
    for (int j = x[i] + 1; j <= r; j++){ //对下一层进行运算
        x[i + 1] = j;
        backtrace(i + 1, r - 1);
        for (int k = 0; k < maxl; k++)
            y[k] = z[k];
    }
}

输入:

2,3

5,4

4,3

输出

 

附一些数据

n= 5 m=4 {1,3,11,15,32} 最大邮资为70

n=5 m=5  {1,4,9,31,51} 最大邮资为126

n=4 m=2 {1,3,5,6}最大邮资为12

n=3 m=4 {1,5,8}最大邮资为26

n=6 m=4{1,4,9,16,38,49}最大邮资为108

n=5 m=6{1,7,12,43,52}最大邮资为216

可以结束了-但是-他还可以用动态规划来解决

设不超过m张面值为x[1:i]的邮票贴出邮资j所需的最少邮票数为y[j]。通过y[j]可以很快推出r的值。事实上,y[j]可以通过递推在O(n)时间内解决:yi(j) 表示最多用 m 张面值 xi 的邮票贴出邮资 j 所需要的最少邮票张数

类似于0-1背包问题:

dfs(x,cur,max)其中x数组存储当前的解有哪些(如果你就是求具体数值,而不要求解的组成甚至可以省略),cur代表目前到第几种面值,当到n为止,max表示到目前为止的最大可到达邮资,而之后下一个x[cur+1]一定是取x[cur]+1~max+1,这个很好理解,max+1目前是访问不到的,那么我加入x[cur+1]后,要看看max更新到多少,而max是从max+1到

m*x[cur+1](最多就是m个最大的那个邮票,最少肯定是要更新,不然要你何用...),接下来的问题就是能否用n种邮票,面值都储存在x数组中,最多贴m张,表示出某个数,

方法是动态规划,状态转移方程dp[i,j]=min(dp[i-1][j-k*a[i]+k),其实有点像背包;(注意边界!!!!!!!)dp[i][0]=0(没有钱时0张邮票)dp[1][i]=i;(因为第一张邮票就是1,贴几块钱就是几张邮票)

#include <stdio.h>
#include <string.h>
int n,m;//n为邮票种类,m为一封信上最多贴的邮票个数
int Max;
int ans[10000];//最终答案数组
int min(int a,int b)
{
    return a<b?a:b;
}
int panduan(int x[10000],int n,int sum)//能否用n种邮票,面值在x数组中,最多贴m张,表示出sum(是个动态规划问题,方法是求出dp[n][sum]看它是否小于sum,状态转移方程dp[i][j]=min(dp[i-1][j-k*x[i]]+k)(其中dp[i][j]表示用到第i种邮票,表示邮资为j的最少邮票
 {
     int i,j,k;
     int dp[15][1005];
     for (i=0;i<=n;i++)
        dp[i][0]=0;
       for (i=0;i<=sum;i++)
        dp[1][i]=i;
     for (i=2;i<=n;i++)
        for (j=1;j<=sum;j++)
     {
         dp[i][j]=9999;
         for (k=0;k<=j/x[i];k++)
            dp[i][j]=min(dp[i][j],dp[i-1][j-x[i]*k]+k);
     }
     if (dp[n][sum]>m)
        return 0;
        return 1;
 
 }
void DFS(int x[10000],int cur,int max)
{
    int i,j,next;
    if (cur==n)//如果已经得出了n种邮票
    {
        if (max>Max)//并且它的最大值已经大于当前最大邮资数
        {
            Max=max;
            for (i=1;i<=cur;i++)
                ans[i]=x[i];//更新答案数组
 
        }
         return;
    }
        for (next=x[cur]+1;next<=max+1;next++)//如果还没得到n中邮票,那么从x[cur]+1~max+1选一个作为下一个邮资,因为max+1没法表示,所以必定到max+1为止
        {
          x[cur+1]=next;//接下来是重点,用种类为cur+1,数目分别为x[1..cur+1]的邮票,最多使用m张,能否表示出大于max的某个数
             for (i=max+1;i<=m*x[cur+1];i++)//这个数最少要为max+1(不然没有意义了),最多是x[cur+1]*m
                if (panduan(x,cur+1,i)==0)//如果成立
                break;
                if (i>max+1)//如果至少让最大值更新了一次
                DFS(x,cur+1,i-1);
       }
 
}
  int main()
  {
      int i,j,max,cur;
      int x[1000];//中间传递的数组,存储当前的邮票值的解
      scanf("%d%d",&n,&m);
      Max=0;
      max=m;
      cur=1;
      x[cur]=1;
      DFS(x,cur,max);//x存储当前的解,cur表示当前传递到第几种邮票,max表示目前能表示到的最大值
      printf("%d\n",Max);
      for (i=1;i<=n;i++)
      printf("%d ",ans[i]);
      return 0;
 
  }

 嘻嘻,抄完了,这个其实我没怎么搞懂,仅仅是大致搞明白了,特别是算r那个地方我不懂。

连续邮资问题详解_羊书change的博客-CSDN博客_连续邮资问题

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

20多年老码农的IT学习之路

20年IT工作经历&#xff0c;目前在一家500强做企业架构&#xff0c;年薪税前150万多&#xff0e;最近公司业绩不好&#xff0c;有感觉工作不保&#xff0c;所以又捡起了编程&#xff0c;开始学习Golang&#xff0c;Angular等。我不是985&#xff0c;211也不是海归&#xff0c;我…

基于ssm+mysql+jsp实现在线花店

基于ssmmysqljsp实现在线花店一、系统介绍1、系统主要功能&#xff1a;2、环境配置二、功能展示1.主页(客户)2.登陆&#xff08;客户&#xff09;3.我的购物车(客户)4.我的订单&#xff08;客户&#xff09;5.主页&#xff08;管理员&#xff09;6.订单管理&#xff08;管理员&…

什么是链路追踪?分布式系统如何实现链路追踪?

在分布式系统&#xff0c;尤其是微服务系统中&#xff0c;一次外部请求往往需要内部多个模块&#xff0c;多个中间件&#xff0c;多台机器的相互调用才能完成。在这一系列的调用中&#xff0c;可能有些是串行的&#xff0c;而有些是并行的。在这种情况下&#xff0c;我们如何才…

PANNs:用于音频模式识别的大规模预训练音频神经网络

摘要 音频模式识别是机器学习领域的一个重要研究课题&#xff0c;它包括音频标注、声音场景分类、音乐分类、语音情感分类和声音事件检测等任务。近年来&#xff0c;神经网络已被应用于解决音频模式识别问题。然而&#xff0c;以前的系统是建立在特定数据集上的&#xff0c;数…

商业化广告--体系学习-- 17 -- 业务实战篇 --平台建设:如何从0到1建立一个完整的广告产品平台?

这是一个非常完整的广告产品平台&#xff0c;它包括广告投放平台&#xff08;代理型和自助型&#xff09;、销售类平台、运营类平台、数据类平台以及流量合作类平台五个部分。我们之前提到过程序化交易的一系列平台&#xff0c;但那些对于一个头部的媒体平台来说并不完整。一个…

结构分析软件:2D Frame Analysis 7.2.6 Crack

结构分析软件&#xff1a;2D Frame Analysis 7.2.6 用于在静态、动态、线性和非线性载荷下对框架、梁和桁架进行结构分析的软件工具。它包括静态版和桁架版的所有功能 2D 框架分析软件套件以及处理动态负载的能力。自动计算结构的动态模态&#xff0c;并以图形方式表示相应的模…

Java图形化界面---

目录 一、JColorChooser &#xff08;1&#xff09;JColorChooser的介绍 &#xff08;2)JColorChooser案列 二、JFileChooser &#xff08;1&#xff09;JFileChooser的介绍 &#xff08;2&#xff09;JFileChooser使用步骤 &#xff08;3&#xff09;JFileChooser案例 …

【云原生进阶之容器】第四章Operator原理4.4节--Operator深入实践

1 Operator 深入实践 在本节中,我们将重点关注 etcd-cluster-operator,用于管理 Kubernetes 内部的 etcd。简单地说,etcd 是一个分布式键值数据存储系统,它有能力管理自己的稳定性,只要: 每个 etcd 实例都有一个用于计算、网络和存储的独立故障域。每个 etcd 实例都有一个…

读 | Software Architecture Patterns

个人博客 Software Architecture Patterns》是 Mark Richards 2015 年出的一本小册子&#xff0c;对常用的架构模式进行了一个简单梳理&#xff0c;书中列了 5 种&#xff1a; 分层&#xff08;Layered&#xff09;事件驱动&#xff08;Event-Driven&#xff09;微内核&#…

HTML的常见标签

什么是 HTML&#xff1f; HTML 是英文 Hyper Text Markup Language&#xff08;超文本标记语言&#xff09;的缩写&#xff0c;是一种用于创建网页的标准标记语言。 什么是HTML 标签? HTML 文档和 HTML 元素是通过 HTML 标签进行标记的 HTML 标签是由尖括号包围的关键词&am…

【阶段四】Python深度学习07篇:深度学习项目实战:循环神经网络的原理和结构

本篇的思维导图: 循环神经网络 循环神经网络,就是专门用于处理语言、文字、时序这类特征之间存在“次序”的问题。这是一种循环的、带“记忆”功能的神经网络,这种网络针对序列性问题有其优势。 循环神经网络的原理和结构 序列数据的概念 序列数据,是其特征的先后…

电路方案分析(十六)高效备用电源设计方案

高效备用电源设计方案 备用电源&#xff1a; 备用电源是一种在主电源发生故障时为负载提供紧急电源的电气系统。适当的备用电源通过提供存储在备用电容器或电池中的能量&#xff0c;提供即时保护&#xff0c;防止主电源中断而产生故障。这种备份电源通常用于保护硬件&#xf…

表单<form>

创建表单 <form> 标签用于创建供用户输入的 HTML 表单。 <form>标签的action属性的值指定了表单提交到服务器的地址。 <form> 元素包含一个或多个如下的表单元素&#xff1a; <input> <textarea> <button> <select> <option&g…

JAVA设计模式--行为型模式--职责链模式

1.责任链模式&#xff08;Chain of Responsibility Pattern&#xff09; 1.1介绍 为请求创建了一个接收者对象的链。这种模式给予请求的类型&#xff0c;对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。在这种模式中&#xff0c;通常每个接收者都包含对…

C语言文件操作(一文带你吃透文件各种操作)

文章目录 一、为什么要使用文件 二、什么是文件 2、1 程序文件 2、2 数据文件 2、3 文件名 三、文件的打开关闭及读写操作 3、1 文件指针 3、2 文件的打开和关闭操作 3、3 文件的读写函数详解 3、3、1 fgetc、fputc函数详解 3、3、2 fgets、fputs函数详解 3、3、3 fscanf、fpri…

神经网络介绍-激活函数、参数初始化、模型的搭建

目录1、深度学习了解1.1 深度学习简介1.2 神经网络2、神经网络的工作流程2.1 激活函数2.1.1 Sigmoid/Logistics函数2.1.2 tanh&#xff08;双曲正切曲线&#xff09;2.1.3 RELU2.1.4 Leaky Relu2.1.5 SoftMax2.1.6 其他激活函数2.2 参数初始化2.2.1 随机初始化2.2.2 标准初始化…

【博客585】ipvs场景下dummy网卡的作用

linux ipvs模式下dummy网卡的作用 1、场景&#xff1a; 在使用ipvs来实现vip的负载均衡的时候&#xff0c;有时我们会在linux中创建一块dummy网卡&#xff0c;并在网卡上绑上vip 2、场景示例&#xff1a;k8s kube-proxy组件的ipvs模式 kube-proxy在ipvs模式下生成了一块kub…

iperf工具源码下载、编译、编译报错解决、以及测试网络带宽

1、iperf源码下载 (1)源码下载地址&#xff1a;https://iperf.fr/iperf-download.php; (2)有的版本源码下载下来并不能直接编译成功&#xff0c;可能会报缺少头文件或者编译选项的错误&#xff0c;要么去解决这些错误&#xff0c;要么换个版本再试一下&#xff1b; (3)在我的环…

Linux学习【教程+实操】【超基础】

链接:资料提取码&#xff1a;6klp 今日内容Linux简介Linux安装网卡设置安装SSH连接工具Linux和Windows目录结构对比Linux常用命令Linux命令初体验文件目录操作命令拷贝移动命令打包压缩命令文本编辑命令查找命令Linux软件安装软件安装方式安装jdk&#xff08;采用二进制发布包…

mysql索引字段设计

表字段数量与row大小限制可以参考官方文档 https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html#column-count-limits 复合索引 参考&#xff1a;https://dev.mysql.com/doc/refman/8.0/en/innodb-limits.html 数量限制 一个复合索引最多可以包含16个列&…