r HDU - 3709 Balanced Numbe(数位dp解析)

2023-10-27

题目链接https://vjudge.net/contest/355127#problem/C
Problem Description
A balanced number is a non-negative integer that can be balanced if a pivot is placed at some digit. More specifically, imagine each digit as a box with weight indicated by the digit. When a pivot is placed at some digit of the number, the distance from a digit to the pivot is the offset between it and the pivot. Then the torques of left part and right part can be calculated. It is balanced if they are the same. A balanced number must be balanced with the pivot at some of its digits. For example, 4139 is a balanced number with pivot fixed at 3. The torqueses are 4* 2 + 1 * 1 = 9 and 9*1 = 9, for left part and right part, respectively. It’s your job to calculate the number of balanced numbers in a given range [x, y].
Input
The input contains multiple test cases. The first line is the total number of cases T (0 < T ≤ 30). For each case, there are two integers separated by a space in a line, x and y. (0 ≤ x ≤ y ≤ 1018).
Output
For each case, print the number of balanced numbers in the range [x, y] in a line.

Sample Input

2
0 9
7604 24324 

Sample Output

10
897

翻译
求给定区间 [l,r] 内平衡数的个数
平衡数是指,某个数以某位作为支点,此位左边的 数字*距离 的和与右边相等
例如:4139,以3位支点,左边的距离之和: 4* 2 + 1 * 1,右边的距离之和:9 *1,两边相等,所以4139为平衡数

引入数位dp
数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数

传入的参数

  1. 前导0标记lead

例如我们要从 [0,1000] 找任意相邻两数相等的数,显然 111,222,888 等等是符合题意的数
但是我们发现右端点1000 是四位数
因此我们搜索的起点是 0000 ,这样三位数的记录都变成了如 0111,0222,0888 等等
而这种情况下如果我们直接找相邻位相等,则 0000 符合题意,但是 0111,0222,0888 都变成不符合题意了
所以我们要引入前导0标记

如果当前位 lead=1 而且当前位也是0,那么当前位也归为前导0, pos+1 继续搜;
如果当前位 lead=1 但当前位不是0,则本位作为当前数的最高位, pos+1 继续搜;

LL dfs(int pos,int pre,int st,....,int lead,int limt)
{
    if(pos>len)
        return st;/*剪枝*/
    if(dp[pos][pre][st]..[...]!=-1&&!limt&&!lead)
        return dp[pos][pre][st]..[...];
    LL res=0;/*暂时记录当前的方案数*/
    int up=limt?a[len-pos+1]:9;
    for(int i=0; i<=up; i++)
    {
        if(!i&&lead)
            res+=dfs(...,...,i==up&&limt);/*有前导0,并且当前位也是0*/
        else if(i&&lead)
            res+=dfs(...,...,i==up&&limt);/*有前导0,但当前位不是0,当前位就是最高位*/
        else if(/*根据题意而定的判断*/)
            res+=dfs(...,.....,i==up&&limt)
        }
}
  1. 最高位标记limt

在搜索数位时,搜索范围可能发生变化
举个例子:我们在搜索 [0,555] 的数时,显然最高位搜索范围是 0 ~ 5 ,而后面的位数的取值范围会根据上一位发生变化:
最高位是 1 ~ 4 时,第二位取值为 [0,9]
最高位是 5 时,第二位取值为 [0,5] (再往上取就超出右端点范围了)

为了分清这两种情况,我们引入了 limt 标记:

若当前位 limit=1 而且已经取到了能取到的最高位时,下一位 limit=1 ;(limt=1表示有限制,limt=0表示没有限制
若当前位 limit=1 但是没有取到能取到的最高位时,下一位 limit=0 ;
若当前位 limit=0 时,下一位 limit=0 。
我们设这一位的标记为 limit ,这一位能取到的最大值为 res ,则下一位的标记就是 i==res && limit ( i 枚举这一位填的数)

  1. dp值的记录和使用

数位dp是在记忆化搜索的框架下进行的,每当找到一种情况我们就可以这种情况记录下来,等到搜到后面遇到相同的情况时直接使用当前记录的值。
dp数组的下标表示的是一种状态,只要当前的状态和之前搜过的某个状态完全一样,我们就可以直接返回原来已经记录下来的dp值。
再举个例子
假如我们找 [0,123456] 中符合某些条件的数
假如当我们搜到1000时,dfs从下返上来的数值就是当前位是第 5 位,前一位是 0 时的方案种数,搜完这位会向上反,这是我们可以记录一下:当前位第 5 位,前一位是 0 时,有的方案种数
当我们继续 搜到 1010时,我们发现当前状态又是搜到了第 5 位,并且上一位也是 0 ,这与我们之前记录的情况相同,这样我们就可以不继续向下搜,直接把上次的dp值返回就行了。

返回的dp值必须和当前处于完全一样的状态,这就是为什么dp数组下标要记录 pos,pre 等参量了。

接着上面的例子,范围 [0,123456]
如果我们搜到了 1234 ,我们能不能直接返回之前记录的:当前第 5 位,前一位是 4 时的dp值?
答案是否定的
我们发现,这个状态的dp值被记录时,当前位也就是第 5 位的取值是 [0,9] ,而这次当前位的取值是 [0,5] ,方案数一定比之前记录的dp值要小。
当前位的取值范围为什么会和原来不一样呢?
如果你联想到了之前所讲的知识,你会发现:现在的 limit=1 ,最高位有取值的限制。
因此我们可以得到一个结论:当 limit=1 时,不能记录和取用dp值
完整代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
int a[20];
LL dp[20][20][2000];
int tot;
LL dfs(int pos,int x,int sta,int limt)
{
    if(sta<0)/*支点两边的和<0,相当于剪枝*/
        return 0;
    if(pos==0)/*每一位都枚举完了,如果sta==0,则返回1,否则返回0*/
        return sta==0;
    if(!limt&&dp[pos][x][sta]!=-1)/*最高位没有限制*/
        return dp[pos][x][sta];
    int up=limt?a[pos]:9;
    LL res=0;
    for(int i=0; i<=up; i++)
    {
        int nex=sta+i*(pos-x);/*x是支点的下标,起初pos为数的总长度,pos-x>0,随着pos的减小,pos-x<0满足了支点两边一正一负,状态累加,状态sta=0是目标状态*/
        res+=dfs(pos-1,x,nex,limt&&(i==up));
    }
    if(!limt)
        dp[pos][x][sta]=res;/*位数为pos,支点为x,两边的支点和为sta的个数*/
    return res;
}
LL solve(LL n)
{
    tot=0;
    while(n)
    {
        a[++tot]=n%10;
        n/=10;
    }
    LL sum=0;
    for(int i=1; i<=tot; i++)
        sum+=dfs(tot,i,0,1);/*数的总长度是tot,把第i为当成是支点,两边的支点和为0的方案数*/
    return sum-tot+1;/*00,000,.....,*/
}
int main()
{
    int t;
    memset(dp,-1,sizeof(dp));
    scanf("%d",&t);
    while(t--)
    {
        LL x,y;
        scanf("%lld%lld",&x,&y);
        printf("%lld\n",solve(y)-solve(x-1));/*相当于0~r区间内的数减去0~(l-1)区间内的数*/
    }
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

r HDU - 3709 Balanced Numbe(数位dp解析) 的相关文章

  • 【导航算法】S型速度规划笔记

    S型速度规划笔记 一 S型速度规划逻辑整理 1 根据q1 q0和速度 判断是否在约束条件下 可以规划出S型速度规划 2 假设可以找到合适的S型速度规划 确定相应的参数v max和a max a 判断是否可以达到最大速度 v max 和加速度
  • 动态规划:样例讲解一篇通

    概念讲解 动态规划是把大问题分解成子问题 但不能简单的分解 子问题要具有相同子结构的解 并综合子问题的解 导出大问题的解 问题求解耗时会按问题规模呈幂级数增加 基本方法 为了节约重复求相同子问题的时间 引入一个数组 不管它们是否对最终解有用
  • 模糊匹配之——BK树与拼写纠正

    介绍 拼写纠错功能常常出现在比较高级的文本编辑应用中 例如大家熟知的word 高级一点的IDE例如Jet Brains系列 在一些在线翻译上 也有自动校正拼写的功能 例如谷歌翻译 原理 拼写纠正的实现方式有多种 这里使用的是一种名为BK树的
  • 洛谷 P1026 [NOIP2001 提高组] 统计单词个数

    题目描述 给出一个长度不超过 200 的由小写英文字母组成的字母串 该字串以每行 20 个字母的方式输入 且保证每行一定为 20 个 要求将此字母串分成 k 份 且每份中包含的单词个数加起来总数最大 每份中包含的单词可以部分重叠 当选用一个
  • 动态规划问题——最长上升子序列(LIS)(一)

    原文转载自我的博客benym cn 推荐链接 动态规划问题 最长上升子序列 LIS 二 动态规划问题 最长上升子序列 LIS 三 如 求 2 7 1 5 6 4 3 8 9 的最长上升子序列 我们定义d i i 1 n 来表示前i个数以A
  • LeetCode:动态规划中的子序列问题

    PS 本文是参考代码随想录做的一些笔记 完整版本请戳链接 非常好的教程 本文列举了一些经典题目 特别是编辑距离 基本上的题目解题思路都是一样的 可以说是一个路子 300 最长递增子序列 给你一个整数数组 nums 找到其中最长严格递增子序列
  • 【AcWing30】正则表达式匹配(动态规划)

    dp i j 表示 s 0 i 的字符串与p 0 j 的字符串是否匹配 那么有以下几个转换状态 1 p j 1 是字母 而且与 s i 1 相等 那么当前dp i j 是否匹配就依赖于dp i 1 j 1 2 p j 1 是 那么肯定与s
  • 数字三角形之动态规划(C语言实现)

    算法步骤 1 首先构造三个数组 第一个是存储三角形初始值的数组data 第二个是存储顶点到该点最大值的res 数组 第三个是存储该点上一个点的loc 数组 这里要对res 数组进行初始化 1 2 按照三角形的层次结构 从上到下 从左到右依次
  • 力扣动态规划专题(一)背包理论基础 基础动规题 动规注意点 步骤及C++实现

    文章目录 动态规划 509 斐波那契数 五步骤 代码 70 爬楼梯 五步骤 代码 746 使用最小花费爬楼梯 五步骤 代码 扩展 62 不同路径 动态规划 数论 63 不同路径 II 五步骤 代码 343 整数拆分 五步骤 代码 96 不同
  • 2020算法设计与分析 官方考前模拟卷 参考答案

    算法设计与分析 样例试题 算法设计与分析总结笔记 注 此试题仅供了解题型 和期末考试试题没有任何直接关系 FBI Warning 这套题难度较大 千万不要坏了心态 xj大佬说要是考试那么难他直播粪坑蝶泳 Power By 王宏志教授 5 分
  • 备战2023蓝桥国赛-传纸条

    题目描述 解析 这道题想了我好久 一开始我是想假如只走一条路线 从 1 1 走到 m n 这种问题该怎么解决呢 针对这种问题我是设了dp k i j 表示走了k步到达 i j 的好心程度之和的最大值 然后根据这个来写出转移方程来计算 后面就
  • 备战2023蓝桥国赛-饼干

    题目描述 解析 这道题我想了很多种解决方法 但无一例外都失败了 实在是按照常规线性DP的思路真的想不出来 看了题解之后才知道它是分为三步解决这个问题的 第一步 缩小最优解的范围 先用贪心将最优解缩小到某个较小的范围内 再DP求出精确的最优解
  • ARC105

    C Camels and Bridge 题意 一堆骆驼过桥 每个桥有承重和长度 问骆驼从头到尾的最近距离 假设这时候骆驼的过桥顺序已经安排好 每一个桥相当于一个限制条件 限制了 l r 的最近距离 也就是说 对于每一个骆驼 j 要保证 好了
  • leetcode动态规划总结之01背包和完全背包问题

    1 背包问题分类 其中 除了01背包和完全背包外 leetcode题目中好像还没有涉及其他类型的背包 在这里我就不做总结 2 01背包理论 有N件物品和一个最大承载重量为W 的背包 第i件物品的重量是weight i 其价值是value i
  • C++---背包模型---装箱问题(每日一道算法2023.3.9)

    注意事项 本题是 动态规划 01背包 的扩展题 dp和优化思路不多赘述 题目 有一个箱子容量为 V 同时有 n 个物品 每个物品有一个体积 正整数 要求 n 个物品中 任取若干个装入箱内 使箱子的剩余空间为最小 输入格式 第一行是一个整数
  • 华为od机考真题-数据分类

    while 1 try c b nums list map int input split dp
  • 算法设计与实现--动态规划篇

    什么是动态规划算法 动态规划算法是一种求解复杂问题的方法 通过将原问题分解为相对简单的子问题来求解 其基本思想是将待求解的问题分解为若干个子问题 阶段 按顺序求解子阶段 前一子问题的解 为后一子问题的求解提供了有用的信息 在求解任一子问题时
  • acwing算法提高之动态规划--最长上升子序列模型(上)

    目录 1 基础知识 2 模板 3 工程化 1 基础知识 暂无 2 模板 暂无 3 工程化 题目1 怪盗基德的滑翔翼 有N个数 表示房屋的高度 你可以任意选择一个房屋作为起点 选择朝左飞 或者朝右飞 必须严格递减才能够飞到下一个房屋 求经过的
  • C/C++---------------LeetCode第509. 斐波那契数

    斐波那契数列 题目及要求 暴力递归 备忘录的递归 动态规划 题目及要求 斐波那契数 通常用 F n 表示 形成的序列称为 斐波那契数列 该数列由 0 和 1 开始 后面的每一项数字都是前面两项数字的和 也就是 F 0 0 F 1 1 F n
  • 【数位dp】【动态规划】C++算法:233.数字 1 的个数

    作者推荐 动态规划 C 算法312 戳气球 本文涉及的基础知识点 动态规划 数位dp LeetCode 233数字 1 的个数 给定一个整数 n 计算所有小于等于 n 的非负整数中数字 1 出现的个数 示例 1 输入 n 13 输出 6 示

随机推荐