数位dp(统计特殊整数)

目录

0.题面

1.题目分析

1.1暴力枚举法

1.2数位dp

2.数位dp原理分析

2.1位数不足时的计算

2.2位数相同时的计算

2.2.1.预处理

2.2.2首位数字比n的首位数字小

2.2.3首位数字与n首位数字相同

3.代码演示

4.结语


0.题面

1.题目分析

1.1暴力枚举法

在做一道题的时候,我们最先想到的肯定是暴力的模拟法:也就是暴力枚举1~n中的每一个数字,然后判断他们中的每个数字是否相同。由于这个想法过于直白,我就直接放代码:

int countSpecialNumbers(int n)
{
	int ans = 0;
	for (int i = 1; i <= n; i++)
	{
		int state = 0, pi = i;
		while (pi)
		{
			int last = pi % 10;
			if ((state & (1 << pi)))
				break;

            state |= (1 << pi);
			last /= 10;
		}
		if (!pi)
			ans++;
	}
	return ans;
}

这里解释一下state这个变量,因为int变量一共有32位,而int最大是21亿多,也就是10位数,所以我们可以拿state二进制上的位来做哈希,检索这个数字有没有出现过。如果出现过直接跳出循环,如果没有出现过,就把那个位置的二进制值改为1。当然,这里的n是10的九次方,所以这个办法显然不能在1s之内完成计算,我们必须要另想办法。


1.2数位dp

我们可以通过分析,直接用公式算出结果,比如说我的n是四位数,那么我就可以用公式,直接算出1位数的情况,2位数的情况和3位数的情况。4位数的情况,再用跑循环和递归,这样就可以大大减少次数。当然这里的循环和递归也不是暴力跑法,详情请看原理分析。


2.数位dp原理分析

2.1位数不足时的计算

这里用五位数举例,在千位我们可以填1~9,因为如果填了0那么就不是4位数了,就是3位数了,所以千位有9种填法,接下来看百位,百位就没有0的拘束,就可以填0,但是我们这一位填的数不能与千位相同,所以就是10-1=9种,那么十位就是10-2=8,个位10-3=7。总的情况就是9*9*8*7种。

接下来看三位数,第一位还是可以1~9,也是有9种方法,第二位是0~9但是不能与第一位重复,也就是9种,第三位就是8种,一共9*9*8以此类推……两位数就是9*9。那么我们就可以尝试写出这个循环了:

int ans=0;
if (len >= 2)
{
	//1位数字时就是九种情况
	ans += 9;
	int pow = 9;
	for (int i = 2, k = 9; i < len; i++, k--)
	{
		pow *= k;
		ans += pow;
	}
}

2.2位数相同时的计算

2.2.1.预处理

在计算这个之前,我们需要有一小步的预处理。

我们可以看到,数字位数不同,后续可以填的个数是不一样的,我们不妨假设数字的长度为len,后续可以填的情况是9!/(10-len)! 但是如果我们每次计算都要用阶乘,那么既不方便,速度也不快。所以我这里就用一个数组来保存。

vector<int> d(len);
d[0]=1;
for(int i=1,k=10-len+1;i<len;i++,k++)
{
    d[i]=d[i-1]*k;
}

这里d中括号中间的数字就代表后面有几个数字,d[0]就是后面还有0位数字的意思,后面有0位数字就说明这个数字已经确定了,后续没有其他可能,可能性就是1种。


2.2.2首位数字比n的首位数字小

我们还是拿这张图,如果我们首位填了4,那是不是说明我们后面的4位可以随意填,只要不与前面重复,所以我们可以直接用公式算出总数。不难发现,我们首位填1~3的情况与填4一样。那么我们首位为什么不是0开始的呢?因为从0开始就不是五位数了,而四位数我们已经在前面计算过了,如果把0算进去就会出现重复。所以一共可以填的数字就是first-1

ans+=(first-1)*d[len-1];//这里的d存的就是9!/(10-len)的值。

2.2.3首位数字与n首位数字相同

当我们第一个数字取了5时,我们第二位就不可以取比4大的数字了,那么它也就只有4种选择了,然后我们还需要分析它可以填什么数字。当第二位填了4时,后续也就不能填3了……

我们接着看这个情况,当第二位填了3时后续是不是有可以随意填了。而且我们这个情况与我们的首位填4的情况类似,所以我们就可以采取递归。

当我们填的数字原数字相同时进入递归,当数字小于原数字时直接用公式计算。


3.代码演示

int f(vector<int>& d, int n, int pow, int len, int state)
{
    if (len == 0)
        return 1;

    int first = (n / pow) % 10;

    int ans = 0;
    //取的数字比当前小时,直接计算
    for (int i = 0; i < first; i++)
    {
        if (((1 << i) & state) == 0)
        {
            ans += d[len - 1];
            //cout<<"##"<<endl;
        }
    }

    //相等进递归
    if (((state) & (1 << first)) == 0)
        ans += f(d, n, pow / 10, len - 1, state | (1 << first));

    return ans;
}

int countSpecialNumbers(int n)
{
    //计算n有几位
    int len = 0, pn = n, first = 0, pow = 0;
    //pow用来取第几位数字
    while (pn)
    {
        if (pow == 0)
            pow = 1;
        else
            pow *= 10;

        len++;
        pn /= 10;

        //获取首个数字
        if (pn < 10)
            first = pn;
    }

    vector<int> d(len);
    d[0] = 1;
    for (int i = 1, k = 10 - len + 1; i < len; i++, k++)
    {
        d[i] = d[i - 1] * k;
    }

    int ans = 0;
    //从两位开始计算
    if (len >= 2)
    {
        //1位数字
        ans += 9;
        int pow = 9;
        for (int i = 2, k = 9; i < len; i++, k--)
        {
            pow *= k;
            ans += pow;
        }
    }

    //位数相同且首数字比first小时,直接公式计算
    ans += (first - 1) * d[len - 1];

    int state = 0;


    ans += f(d, n, pow, len, state);
    return ans;
}

4.结语

如果大家对这个内容有啥疑问,欢迎在评论区友好讨论呀~

  • 19
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
内 容 摘 要 电压表是测量仪器中不可缺少的设备,目前广泛应用的是采用专用集成电路实现的数 字电压表。本系统以增强型8051单片机为核心,设计了一款简易的数字电压表,能够测 量0~5V的直流电压,最小分辨率为0.02V。 该设计大体分为以下几个部分,同时,各部分选择使用的主要元器件确定如下: 1、单片机部分。使用增强型8051单片机,同时根据需要设计单片机电路。 2、测量部分。该部分是实验的重点,要求将外部采集的模拟信号转换成数字信号, 通过单片机的处理显示在显示器上,该部分决定了数字电压表的精度等主要技术指标。 3、键盘显示部分。利用4×6矩阵键盘的一个按键控制量程的转换,3或4位LED显示。 其中一位为整数部分,其余位小数部分。 索引关键词:增强型8051 模数转换 LED显示 矩阵键盘 目 录 一 概 述 …………………………………………………………………4 二 方案设计与论证 ……………………………………………………………4 三 单元电路设计与参数计算 …………………………………………………4 3.1. A D转换器 ……………………………………………………5 3.1. LED数码显示 ………………………………………………………7 四 总原理图及参考程序 ………………………………………………………9 五 结论 …………………………………………………………………………10 六 心得体会 ……………………………………………………………………14 七 参考文献 ……………………………………………………………………15 一、概述 数字电压表的基本工作原理是利用A/D转换电路将待测的模拟信号转换成数字信号, 通过相应换算后将测试结果以数字形式显示出来的一种电压表。较之于一般的模拟电压 表,数字电压表具有精度高、测量准确、读数直观、使用方便等优点。 电压表的数字化测量,关键在于如何把随时连续变化的模拟量转化成数字量,完成这 种转换的电路叫模数转换器(A/D)。数字电压表的核心部件就是A/D转换器,由于各种 不同的A/D转换原理构成了各种不同类型的DVM。一般说来,A/D转换的方式可分为两类: 积分式和逐次逼近式。 积分式A/D转换器是先用积分器将输入的模拟电压转换成时间或频率,再将其数字化 。根据转化的中间量不同,它又分为U-T(电压-时间)式和U-F(电压- 频率)式两种。 逐次逼近式A/D转换器分为比较式和斜坡电压式,根据不同的工作原理,比较式又分 为逐次比较式及零平衡式等。斜坡电压式又分为线性斜坡式和阶梯斜坡式两种。 在高精度数字电压表中,常采用由积分式和比较式相结合起来的复合式A/D转换器。 本设计以增强型8051单片机为核心,构造了一款简易的数字电压表,能够测量1路0~5V 直流电压,最小分辨率0.02V。 二、方案设计与论证 该设计是基于增强型8051的数字电压表,大体分为以下几个部分,同时,各部分选择 使用的主要元器件确定如下: (1)单片机部分 使用增强型8051单片机,同时根据需要设计单片机电路。 (2)测量部分 该部分是实验的重点,要求将外部采集的模拟信号转换成数字信号,通过单片机的处理 显示在显示器上,该部分决定了数字电压表的精度等主要技术指标。 (3)键盘显示部分 利用4×6矩阵键盘的一个按键控制量程的转换,3或4位LED显示。其中一位为整数部分, 其余位小数部分。 三、单元电路设计与参数计算 3.2 LED数码显示 (1)LED显示器 LED是由若干个发光二极管组成的。当发光二极管导通时,相应的一个点或一个笔划 发亮。控制不同组合的二极管导通,就能显示出各种字符。这种笔划式的七段显示器, 能显示的字符数量少,但控制简单、使用方便。 发光二极管的阳极连在一起的称为共阳极显示器,阴极连在一起的称为共阴极显示器 (2)LED结构及显示原理 通常的七段LED显示块中有八个发光二极管,故也有人叫做八段显示块。其中七个发 光二极管构成七笔字形"8"。一个发光二极管构成小数点。七段显示块与单片机接口非常 容易。只要将一个8位并行输出口与显示块的发光二极管引脚相连即可。8位并行输出口 输出不同的字节数据即可获得不同的数字或字符。通常将控制发光二极管的8位字节数据 称为段选码或段数据。 (3)LED的结构及其工作原理 点亮显示器有静态和动态两种方法。 1)静态显示:当显示某一个字符时,相应的发光二极管恒定地导通或截止。例如七段显 示器的a、b、c、d、e、f导通,g、dp截止,显示0。 静态显示的特点是: 每一位都需要一个8位输出口控制,用于显示位数较少(仅一、二位)的场合。 较小的电流能得到较高的亮度,可以由8255的输出口直接驱动。 2)动态显示:一位一位地轮流点亮各位显示器(扫描)。对于每一位显示器来说,每 隔一段时

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值