[您有新的未分配科技点]数位dp:从懵X到板子(例题:HDU2089 不要62)

时间:2022-09-09 22:52:06

数位dp主要用来处理一系列需要数数的问题,一般套路为“求[l,r]区间内满足要求的数/数位的个数”

要求五花八门……比如“不出现某个数字序列”,“某种数的出现次数”等等……

面对这种数数题,暴力的想法是枚举每个数,判断是否满足条件

比如这样:

#include<cstdio>
using namespace std;
typedef long long LL;
LL l,r,cnt;
int main()
{
scanf("%lld%lld",&l,&r);
for(LL i=l;i<=r;i++)
if(/*i符合条件*/)cnt++;
printf("%lld",cnt);
}

这样很显然会T......所以我们考虑利用一些奇怪的性质来数数(一般这些性质可以用来递推、或是dp一样的转移)

比如看下面一道例题:

对于给定闭区间[L,R],求非0数位出现的个数

sample input:23 233

sample output: 515

首先转化为calc[1~R]-calc[1~L-1](我们设数字的最低位为第1位,次低为位第2位,以此类推)

做法1:专门统计数位的方法:我们先预处理bin[i]为10的i次方,再预处理dp[i]表示在i位数的范围内(1~99...999(i个9))某种数字的个数

那么考虑dp[i]和dp[i-1]之间的转移:

首先,第i位的数字为0~9时都可以对第i位数字的dp值产生dp[i-1]的贡献

也即:[0~10...(i-1个0)...0)的末i-1位贡献+[10...(i-1个0)...0~20...(i-1个0)...0)的末i-1位贡献+……+[90...(i-1个0)...0~10...0(i个0))的末i-1位贡献

接着,后面i-1位为任意数是都会给第i位的数字产生+1的贡献,由排列组合知总共有bin[i-1]的贡献

所以转移是dp[i]=10*dp[i-1]+bin[i-1](其实化简一下式子,也可以写成dp[i]=i*bin[i-1])

有了这个dp数组我们考虑如何计算对于某个数字x计算[1~x]某个数位st的出现个数,这个过程和之前递推的过程很相似

我们枚举x的第i位位bit

1° bit>st 第i位取0~bit时都可以增加dp[i-1]的贡献,而0~bit里肯定会有这一位取st的情况,贡献加上bin[i-1]

因此ans+=bin[b-1]+d*dp[b-1];

2° bit==st 设tail=x%bin[i-1](x的前i-1位数的值)第i位取0~bit时都可以增加dp[i-1]的贡献,但是当第i位取st(bit)时,只有tail+1种数比x小,因此贡献只有tail+1

此时ans+=tail+1+d*dp[b-1];

3°bit<st 第i位取0~bit时都可以增加dp[i-1]的贡献,但0~bit取不到st

所以ans+=d*dp[b-1];

但是在统计完答案之后,如果我们统计的st==0,前导0被多统计了(第i位不能取0,因此多加了bin[0]+bin[1]+...+bin[数的位数-1]),在最后减去即可

代码见下:

 #include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
LL l,r,bin[],dp[];
inline void intn()
{
bin[]=;//bin[i]是10的j次方
for(int i=;i<=;i++)
bin[i]=bin[i-]*,dp[i]=dp[i-]*+bin[i-];
//第i位是0~9的时候,都会有+dp[i-1]的新贡献
//而后面i-1位任一情况时都会给第i位的数字贡献+1,一共有bin[i-1]种情况
}
inline LL calc(LL sum,int state)
{
LL tmp=sum;
int b=,d;LL ans=;//tail是末几位的数字大小
while(sum)
{
d=sum%;sum/=,b++;
if(d>state)ans+=bin[b-]+d*dp[b-];
//对本位(指第i位)有bin[b-1](末i-1位随意选择)的贡献,第i位是0~d的时候都会有贡献
else if(d==state)ans+=(tmp%bin[b-])++d*dp[b-];
//本位的贡献仅限于末位的数字大小+1(全0也会提供贡献)
else ans+=d*dp[b-];//本位没有贡献
}
d=;
if(state==)
//前导0被重复计算了,一位数多算了一个0,两位数多算了10个零,如此我们减去多算的就好了
while(tmp)
ans-=bin[d++],tmp/=;
return ans;
}
inline LL work(LL data)
{
LL ans=;
for(int i=;i<=;i++)
ans+=calc(data,i);
return ans;
}
int main()
{
scanf("%lld%lld",&l,&r);
intn();
LL ansr=work(r);
if(l==)printf("%lld",ansr);
else printf("%lld",ansr-work(l-));
}

做法2:正经(?)dp法

我们设f[i][j]为x的前i位数并且第i位数为j时j的出现次数,依然预处理bin[i]同上

那么类别上面的做法

首先f[i][j]+=Σ(f[i-1][k],0<=k<=9);接着,如果j不是0,我们在加上第i位对这个数的贡献bin[i-1](其实就实现了上面统计时最后消去前导0的过程)

要注意的一点是[0~10...(i-1个0)...0)等都是一个左闭右开区间,如果计算work(R)-work(L-1),就无法统计R的贡献

因此我们计算时也使用左闭右开区间,计算work(R+1)-work(L)就好啦

代码见下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
LL l,r,bin[];
LL f[][];//f[i][j]表示前i位,第i位数字为j的j出现次数
int bit[];
inline void intn()
{
bin[]=;//bin[i]是10的j次方
for(int i=;i<=;i++)
bin[i]=bin[i-]*;
for(int i=;i<=;i++)
for(int j=;j<;j++)
{
for(int k=;k<;k++)
f[i][j]+=f[i-][k];
f[i][j]+=(j==)?:bin[i-];
}
}
inline LL work(LL x){
int cnt=,b=;while(x)bit[++b]=x%,x/=;//bit表示每一位
LL ans=;
for(int i=b;i;i--)
{
for(int j=;j<bit[i];j++)
ans+=f[i][j];//第i位是0~bit[i]-1的情况
ans+=bin[i-]*bit[i]*cnt;//第i位是bit[i]的情况
//这里的cnt记录了“第i位以前(第i+1位到最高位)有多少个数不是0”,因为他们也算是非0数位。
if(bit[i])cnt++;
}
return ans;
}
int main()
{
scanf("%lld%lld",&l,&r);
intn();
LL ansr=work(r+);
if(l==)printf("%lld",ansr);
else printf("%lld",ansr-work(l));
}

下面我们再来一道例题:[HDU2089]不要62

不要62

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)

Problem Description
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。不吉利的数字为所有含有4或62的号码。例如:62315 73418 88914都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
Input
输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。
Output
对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。
Sample Input
1 100 0 0
Sample Output
80
 
题解:
我们依然定义f[i][j]数组,为前i位数第i位为j时的合法数的数量
初始化时f[0][0]=1;
那么显然当且仅当j!=4&&!(j==6&&k==2)时,能有转移f[i][j]+=f[i-1][k];
统计答案时,我们还是从高位向低位统计,当我们枚举的数位j满足j!=4&&!(x的第i+1位==6&&j==2)时可以统计答案。
需要注意的是:如果我们发现原数中的某一处出现了62或者4,我们直接结束统计,因为在高位相等的前提下更低的位数一定不合法了。
代码见下:
 #include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
int l,r,bit[],f[][];
inline void intn()
{
f[][]=;
for(int i=;i<=;i++)
for(int j=;j<;j++)
{
if(j==)continue;
for(int k=;k<;k++)
{
if(j==&&k==)continue;
f[i][j]+=f[i-][k];
}
}
}
inline int work(int x)
{
memset(bit,,sizeof(bit));
int b=,cnt=,ans=;
while(x)bit[++b]=x%,x/=;
for(int i=b;i;i--)
{
for(int j=;j<bit[i];j++)
{
if(j==||(bit[i+]==&&j==))continue;
ans+=f[i][j];
}
if(bit[i]==||(bit[i+]==&&bit[i]==))break;
}
return ans;
}
int main()
{
intn();
while(scanf("%d%d",&l,&r)==)
{
if(l==r&&r==)break;
printf("%d\n",work(r+)-work(l));
}
}

[您有新的未分配科技点]数位dp:从懵X到板子(例题:HDU2089 不要62)的更多相关文章

  1. &lbrack;您有新的未分配科技点&rsqb;数位DP:从板子到基础(例题 bzoj1026 windy数 bzoj3131 淘金)

    只会统计数位个数或者某种”符合简单规律”的数并不够……我们需要更多的套路和应用 数位dp中常用的思想是“分类讨论”思想.下面我们就看一道典型的分类讨论例题 1026: [SCOI2009]windy数 ...

  2. &lbrack;您有新的未分配科技点&rsqb; 无旋treap:从单点到区间(例题 BZOJ1500&amp&semi;NOI2005 维护数列 )

    1500: [NOI2005]维修数列 Time Limit: 10 Sec  Memory Limit: 64 MB Description Input 输入的第1 行包含两个数N 和M(M ≤20 ...

  3. &lbrack;您有新的未分配科技点&rsqb;博弈论进阶:似乎不那么恐惧了…… (SJ定理,简单的基础模型)

    这次,我们来继续学习博弈论的知识.今天我们会学习更多的基础模型,以及SJ定理的应用. 首先,我们来看博弈论在DAG上的应用.首先来看一个小例子:在一个有向无环图中,有一个棋子从某一个点开始一直向它的出 ...

  4. &lbrack;您有新的未分配科技点&rsqb;博弈论入门:被博弈论支配的恐惧(Nim游戏,SG函数)

    今天初步学习了一下博弈论……感觉真的是好精妙啊……希望这篇博客可以帮助到和我一样刚学习博弈论的同学们. 博弈论,又被称为对策论,被用于考虑游戏中个体的预测行为和实际行为,并研究他们的应用策略.(其实这 ...

  5. &lbrack;您有新的未分配科技点&rsqb;无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

    今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和treap一样简单易懂,同时还支持可持久化. 无旋treap的节点定义和treap一样,都要同时满足树性质和堆性质,我 ...

  6. &lbrack;您有新的未分配科技点&rsqb;可,可,可持久化!?------0-1Trie和可持久化Trie普及版讲解

    这一次,我们来了解普通Trie树的变种:0-1Trie以及在其基础上产生的可持久化Trie(其实,普通的Trie也可以可持久化,只是不太常见) 先简单介绍一下0-1Trie:一个0-1Trie节点只有 ...

  7. &lbrack;您有新的未分配科技点&rsqb;&lbrack;BZOJ3545&amp&semi;BZOJ3551&rsqb;克鲁斯卡尔重构树

    这次我们来搞一个很新奇的知识点:克鲁斯卡尔重构树.它也是一种图,是克鲁斯卡尔算法求最小生成树的升级版首先看下面一个问题:BZOJ3545 Peaks. 在Bytemountains有N座山峰,每座山峰 ...

  8. 数位DP 详解

    序 天堂在左,战士向右 引言 数位DP在竞赛中的出现几率极低,但是如果不会数位DP,一旦考到就只能暴力骗分. 以下是数位DP详解,涉及到的例题有: [HDU2089]不要62 [HDU3652]B-n ...

  9. Elasticsearch 学习之 分片未分配原因

    分片未分配的原因主要有: 1)INDEX_CREATED:由于创建索引的API导致未分配.2)CLUSTER_RECOVERED :由于完全集群恢复导致未分配.3)INDEX_REOPENED :由于 ...

随机推荐

  1. CentOS7 监控进程网络流量工具安装

    服务器在做测试的时候,需要监控网络流量,用来了解在不同人数的时候服务器的网络使用量. 我们使用服务器环境是centos7,centos下通常使用iftop,或者nethogs来进行网络流量监控.这2个 ...

  2. 游戏开发之UE4添加角色到场景中

    接着上次继续学习,现在我们已经有了一个场景并且运行了,我们需要添加一个角色到场景中.要这样做,我们必须从UE4的GameFramework类继承它. 一. 创建一个从Character类继承的类 从基 ...

  3. Zabbix通过JMX方式监控java中间件

    Zabbix2.0添加了支持用于监控JMX应用程序的服务进程,称为“Zabbix-Java-gateway”:它是用java写的一个程序. 工作原理: zabbix_server想知道一台主机上的特定 ...

  4. Jmeter卡住解决方案

    windows环境下,修改jmeter.bat: set HEAP=-Xms256m -Xmx256m set NEW=-XX:NewSize=128m -XX:MaxNewSize=128m 改为: ...

  5. Centos7&period;4别名设置提高工作效率

    一.打开 .bashrc文件 1.位置:~(cd ~)目录下 2.cat .bashrc 原文件内容如下: # .bashrc # User specific aliases and function ...

  6. python安装scrapy

    Scrapy基于事件驱动网络框架 Twisted 编写,Twisted是一个异步非阻塞框架. 安装 scrapy 要先安装 Twisted,不然无法安装成功,链接: Python Extension ...

  7. 12章 搜索框架ElasticSearch介绍和整合SpringBoot 4节课

    1.搜索引擎知识和搜索框架elasticsearch基本介绍     简介:通过京东电商 介绍什么是搜索引擎,和开源搜索框架ElasticSearch6.x新特性介绍 前言:介绍ES的主要特点和使用场 ...

  8. Amazon

    刚接到Recruiter电话,说恭喜,feedback都非常好. 心里大石落地,FLAG / UAT终于完成一家. 接下来就要加倍努力冲刺其他公司了. Mark: (入职以后一定要去地里补发一波面经, ...

  9. 《JavaScript》高级程序设计第7章 函数表达式

    7.2 闭包 定义: 闭包是指有权访问另一个函数作用域中的变量的函数. 理解闭包: 作用域链: 当某个函数被调用时,会创建一个执行环境以及相应的作用域链. 作用域链中,外部函数的活动对象始终处于第二位 ...

  10. Python中第三方库的安装

    网上的帖子挺多的,教你如何安装,安装第三方工具库的方法总共分为三类:Dos系统下pip命令:安装包下载安装:IDE集成环境下安装(Pycharm,Spyder……) http://www.jiansh ...