数位dp:处理数字数位关系的一种dp方式。

一般的题目特征十分明显:

1.一般和数字本身有很大关系。

2.一般求数字在区间L,R中的一些信息

3.L,R一般很大,通常能达到long long级别。

dp方式也比较有套路:

一般有三种方法:

本质上的相似之处,都是集中在处理“填数有无限制”,“填数无限制情况下的固定方案数”,“某些已经搜出来的固定方案数”

1.记忆化搜索(没用过)

是一种倒着记忆的方法。

2.递推(我基本都是这个方法)

正着递推出答案。

一般会从高位向低位递推,讨论高位的填数方法,从而递推出低位的限制与否。

从高位到低位可以通过填的位数处理限制问题。

用一个[0/1]表示,填完这一位之后,是否对后面的数有限制。

填一个数,从k<num,k==num,k>num 讨论。

注意处理可能出现的前导零的锅。

3.预处理一些固定没有限制的一些位置,然后从高位开始填,

没有限制的话,直接查表。

有限制的话,继续填下一位。

注意处理已经填好的位置的固定影响。

经典入门例题:

[ZJOI2010]数字计数

给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。

直接做就好了。

可以处理1~9,1~99,。。的固定答案,或者直接递推下去。

处理1~9,1~99,1~999...的方法:

Code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=;
ll a,b;
ll cnta[],cntb[];
ll f[],ten[];
void query(ll x,ll *cnt)
{
int len=;
int num[];
ll k=x;
while(k)
{
num[++len]=k%;
k/=;
}
for(int i=len;i>=;i--)
{
for(int j=;j<=;j++)
cnt[j]=cnt[j]+f[i-]*num[i];
for(int j=;j<num[i];j++)
cnt[j]+=ten[i-];
ll num2=;
for(int j=i-;j>=;j--)
{
num2=num2*+num[j];
}
num2++;
cnt[num[i]]+=num2;
cnt[]-=ten[i-];
}
}
int main()
{
scanf("%lld%lld",&a,&b);
ten[]=;
for(int i=;i<=;i++)
{
f[i]=f[i-]*+ten[i-];
ten[i]=ten[i-]*;
}
query(a-,cnta);
query(b,cntb);
for(int i=;i<=;i++)
printf("%lld ",cntb[i]-cnta[i]);
return ;
}

数字计数1

递推处理:

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
struct node{
ll a[];
void init(){
memset(a,,sizeof a);
}
node operator +(const node &b){
node c;c.init();
for(int i=;i<=;i++)
c.a[i]=a[i]+b.a[i];
return c;
}
node operator *(const int &x){
node c;c.init();
for(int i=;i<=;i++)
c.a[i]=a[i]*x;
return c;
}
node operator -(const node &b){
node c;c.init();
for(int i=;i<=;i++)
c.a[i]=a[i]-b.a[i];
return c;
}
void op(){
cout<<endl;
for(int i=;i<=;i++)
cout<<" "<<a[i];cout<<endl;cout<<endl;
}
}f[N][],ans,kk;
int num[N],cnt;
ll ten[N];
ll pre[N];
ll A,B;
void sol(){
for(int i=;i<=cnt+;i++)
for(int j=;j<=;j++)
f[i][j].init();
for(int i=cnt;i>=;i--){
//cout<<num[i]<<" dig"<<endl;
f[i][]=f[i+][];f[i][].a[num[i]]++;
f[i][]=(f[i+][]*num[i])+(f[i+][]*);
for(int j=;j<=;j++)
{
if(j<num[i]) f[i][].a[j]+=(pre[i+]+);
else f[i][].a[j]+=pre[i+];
}
//f[i][1].op();
//f[i][0].op();
}
for(int i=cnt;i>=;i--){
f[][].a[]-=ten[i-];
}
}
int main()
{
scanf("%lld%lld",&A,&B);ten[]=;
for(int i=;i<=;i++)ten[i]=ten[i-]*(ll);
while(B){
num[++cnt]=B%;
pre[cnt]=B;B/=;
}
//pre[cnt+1]=1;
sol();
ans=ans+f[][]+f[][]; cnt=;memset(num,,sizeof num);
memset(pre,,sizeof pre);
A--;
while(A){
num[++cnt]=A%;
pre[cnt]=A;
A/=;
}
//pre[cnt+1]=1; sol();
kk=kk+f[][]+f[][];
ans=ans-kk; for(int i=;i<=;i++){
printf("%lld ",ans.a[i]);
}
return ;
}

数字计数2

[SCOI2009]windy数

windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,

在A和B之间,包括A和B,总共有多少个windy数?

Solution:

直接记录最后一个数是什么,要记录是否已经出现了非零数字,即前导零。处理前导零bug

因为前导零不处理,可能会认为最后一位填的是0,从而不能填1、0了。

f[i][last][0/1限制][0/1有无出现非零数字]

Code:

#include<bits/stdc++.h>
using namespace std;
const int N=;
int f[N][N][][];
int num[N],cnt;
int A,B;
int wrk(){
memset(f,,sizeof f);
for(int i=;i<num[cnt];i++) f[cnt][i][][]=;
f[cnt][num[cnt]][][]=;
f[cnt][][][]=; for(int i=cnt;i>=;i--){
for(int j=;j<=;j++){
for(int k=;k<=;k++){
if(abs(k-j)>=){
if(j==num[i]&&k==num[i-]) f[i-][k][][]+=f[i][j][][];
if(j==num[i]&&k<num[i-]) f[i-][k][][]+=f[i][j][][];
f[i-][k][][]+=f[i][j][][];
if(j==) f[i-][k][][]+=f[i][j][][];
}
}
}
f[i-][][][]+=f[i][][][];
f[i-][][][]+=f[i][][][];
}
int ret=;
for(int j=;j<=;j++){
ret+=f[][j][][]+f[][j][][];
}
//ret+=f[1][0][0][0];
return ret;
}
int main()
{
scanf("%d%d",&A,&B);
while(B){
num[++cnt]=B%;B/=;
}
int ansB=wrk(); A--;cnt=;
while(A){
num[++cnt]=A%;A/=;
}
int ansA=wrk(); //cout<<ansB<<" "<<ansA<<endl; printf("%d",ansB-ansA);
return ;
}

windy数

SAC#1 - 萌数

辣鸡蒟蒻SOL是一个傻逼,他居然觉得数很萌!

好在在他眼里,并不是所有数都是萌的。只有满足“存在长度至少为2的回文子串”的数是萌的——也就是说,101是萌的,因为101本身就是一个回文数;110是萌的,因为包含回文子串11;但是102不是萌的,1201也不是萌的。

现在SOL想知道从l到r的所有整数中有多少个萌数。

由于答案可能很大,所以只需要输出答案对1000000007(10^9+7)的余数。

Solution:

抓住问题本质,长度至少为2的回文子串,只要有2或者3就一定满足。

所以,只要记录上一位,上上位填的数即可。

还要记录是否已经出现过长度为2或者3的回文串。

同样,前导零的锅也要处理的。

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=+;
const int mod=1e9+;
int n,m;
char a[N],b[N];
int la,lb;
ll ans;
ll r,l;
ll f[N][][][][][];// lim ; has huiwen ; has >=1
ll wrk(char *s,int len){
memset(f,,sizeof f);
for(int j=;j<=s[]-'';j++){
if(j!=){
if(j<s[]-'')f[][][][][j][]=;
else f[][][][][j][]=;
}
else f[][][][][][]=;
}
if(len==) return ;
for(int i=;i<=lb;i++){
for(int j=;j<=;j++){
for(int k=;k<=;k++){
for(int p=;p<=;p++){
if(p<s[i]-''){ if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][]+f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][]+f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][]+f[i-][][][j][k][])%=mod;
}
else if(p==s[i]-''){
if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][])%=mod; if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
}
else{
if(p==k||p==j) (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
else (f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
(f[i][][][k][p][]+=f[i-][][][j][k][])%=mod;
}
}
}
}
for(int p=;p<=;p++){
if(p==) (f[i][][][][p][]+=f[i-][][][][][])%=mod;
else (f[i][][][][p][]+=f[i-][][][][][])%=mod;
}
}
ll ret=;
for(int j=;j<=;j++)
for(int k=;k<=;k++){
(ret+=f[len][][][j][k][]+f[len][][][j][k][])%=mod;
}
return ret;
}
int main()
{
scanf("%s",a+);scanf("%s",b+);
la=strlen(a+),lb=strlen(b+); l=wrk(a,la);
r=wrk(b,lb); bool fl=false;
for(int i=;i<=la;i++){
if(a[i]==a[i-]||a[i]==a[i-]) fl=true;
}
ans=(r+fl-l+mod)%mod;
printf("%lld",ans);
return ;
}

萌数

bzoj 3329 Xorequ

Description

Input

第一行一个正整数,表示数据组数据 ,接下来T行
每行一个正整数N

Output

2*T行
第2*i-1行表示第i个数据中问题一的解,

第2*i行表示第i个数据中问题二的解,

HINT

x=1与x=2都是原方程的根,注意第一个问题的解

不要mod 10^9+7

1<=N<=10^18

1<=T<=1000

Solution:

首先一定要化简x^3x=2x

即:3x=x^2x ; x+2x=x^2x 说明,2倍的x和x没有公共1位置,否则就会变小了。

2x就是x二进制下左移一位,所以,x的条件其实是,x的二进制表示下没有连续的两个1.

对于第一问,记录上一位填的是0/1即可转移。

对于第二问,发现,是2的整次幂。2^n显然满足条件,

这里假设可以是0,不需要正整数条件,而0显然也满足条件。

最后也不用减,因为2^n本来就要加1个。

即处理有n位可以填0/1。

设f[i]表示,i位随便填0/1,符合的数的个数。

第i位填1的话,i-1位只能填零,方案数就是f[i-2]

第i位填0的话,对i-1位没有影响,就是f[i-1]

所以f[i]=f[i-1]+f[i-2],f[1]=2,f[2]=3

一个斐波那契数列。

矩阵乘法优化一下即可。

Code:(多组数据记得memset)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const int mod=1e9+;
ll f[N][][];
int num[N],cnt;
ll wrk1(){
memset(f,,sizeof f);
f[cnt+][][]=;
for(int i=cnt;i>=;i--){
for(int k=;k<=;k++){
if(k<num[i]){
if(k==) f[i][][k]+=f[i+][][]+f[i+][][];
else f[i][][k]+=f[i+][][]+f[i+][][]+f[i+][][]+f[i+][][];
}
else if(k==num[i]){
if(k==) f[i][][k]+=f[i+][][],f[i][][k]+=f[i+][][];
else f[i][][k]+=f[i+][][]+f[i+][][],f[i][][k]+=f[i+][][]+f[i+][][];
}
else {
if(k==) f[i][][k]+=f[i+][][];
else f[i][][k]+=f[i+][][]+f[i+][][];
}
}
}
ll ret=;
ret=f[][][]+f[][][]+f[][][]+f[][][];
return ret-;
}
struct tr{
ll a[][];
void pre(){
memset(a,,sizeof a);
}
void init(){
for(int i=;i<=;i++) a[i][i]=;
}
tr operator *(const tr &b){
tr c;c.pre();
for(int i=;i<=;i++)
for(int k=;k<=;k++)
for(int j=;j<=;j++){
(c.a[i][j]+=a[i][k]*b.a[k][j]%mod)%=mod;
}
return c;
}
}A,S,B;
tr qm(tr x,ll y){
tr ret;ret.pre();ret.init();
while(y){
if(y&) ret=ret*x;
x=x*x;
y>>=;
}
return ret;
}
int t;
ll n;
int main()
{
scanf("%d",&t);
A.a[][]=,A.a[][]=;
B.a[][]=,B.a[][]=;
B.a[][]=,B.a[][]=; while(t--){
scanf("%lld",&n);
ll nn=n;
cnt=;
while(nn){
num[++cnt]=nn%;nn/=;
}
ll ans1=wrk1();
printf("%lld\n",ans1); ll ans2;
if(n==){
ans2=;
}
else if(n==){
ans2=;
}
else{
S=A*qm(B,n-);
ans2=S.a[][]%mod;
}
printf("%lld\n",ans2);
}
return ;
}

Xorequ

CF55D Beautiful numbers

Volodya是一个很皮的男孩。他认为一个能被它自己的每一位数上的数整除(非0)的数是很妙的。我们先忽略他的想法的正确性,只回答在l到r之间有多少个很妙的数字。

输入输出格式

输入:总共有t个询问:

第一行:t;

接下来t行:每行两个数l和r。

注意:请勿使用%lld读写长整型(虽然我也不知道为什么),请优先使用cin(或者是%I64d)。

输出:t行,每行为一个询问的答案。

Solution:

比较巧妙的状态设计,还有优化 的题目。

直接记录被1,2,3..除是肯定不行的,。

发现,lcm(1,2.。。9)=2520

所以,如果最后能被1,2.。。9整除,那么把这个数对2520取模,不会影响整除与否的。

所以,f[i][2520][S]表示,填到i位,对2520取模结果,出现数字的状压集合。

但是,这个题有10组数据,就T飞了。

考虑优化没用的状态。

S大小是256的,不用记0,不用记1,要记8个。

但是,最后要找的还是S出现数字的lcm能否整除x

所以,就直接记录lcm就行了。

2250=2^3*3^2*5*7,一共只有48种lcm,离散化记录,256->48

但是还是过不去(可能我的代码常数太大?)

发现,记录mod 2250的余数时,假设之前是j,

那么,新的=j*10+k mod 2250(k是当前位的数)

设x=a2250+j;

每次j要乘10,是不是记录mod 225即可?

x=a'225 + j'

x'=a'2250+10j'+k

发现,x mod 2250 的只和j‘有关,

所以,记录j'即可,每次mod 225记录新的j

最后一次,mod 2250 就是mod 2250 的余数了。

Code:(luogu最优解)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=;
const int up=;
ll L,R;
int a[N],cnt;
ll f[N][][][];
ll g[][][];
int lcm[],tot;
int id[];
int to[][];
int gcd(int p,int q){
return q?gcd(q,p%q):p;
}
void dfs(int x,int lc){
//cout<<" x lc "<<x<<" "<<lc<<endl;
if(x==){
lcm[++tot]=lc;
return;
}
dfs(x+,lc);
dfs(x+,lc*x/gcd(x,lc));
}
ll wrk(){
ll ret=;
memset(f,,sizeof f);
memset(g,,sizeof g);
f[cnt+][][][]=;
for(int i=cnt;i>=;i--){
for(int j=;j<;j++){
for(int k=;k<=tot;k++){
for(int p=;p<=;p++){
if(i!=){
if(p<a[i]){
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k]+f[i+][j][][k]; }
else if(p==a[i]){
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k];
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k];
}
else {
f[i][(j*+p)%up][][to[k][p]]+=f[i+][j][][k];
}
}
else{
if(p<a[i]){
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k]+f[i+][j][][k]; }
else if(p==a[i]){
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k];
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k];
}
else {
g[(j*+p)%][][to[k][p]]+=f[i+][j][][k];
}
}
}
}
}
}
for(int j=;j<;j++){
for(int k=;k<=tot;k++){
if(j%lcm[k]==){
ret+=g[j][][k]+g[j][][k];
}
}
} return ret;
}
int T;
ll ansl,ansr;
int main()
{
dfs(,);
sort(lcm+,lcm+tot+);
//cout<<tot<<endl;
tot=unique(lcm+,lcm+tot+)-lcm-;
for(int i=;i<=tot;i++) id[lcm[i]]=i;
for(int i=;i<=tot;i++){
to[i][]=i;
for(int j=;j<=;j++){
to[i][j]=id[lcm[i]*j/gcd(lcm[i],j)];
}
} /*cout<<" tot "<<tot<<endl;
for(int i=1;i<=tot;i++){
cout<<lcm[i]<<" "<<id[lcm[i]]<<" "<<endl;
for(int j=0;j<=9;j++){
cout<<" with "<<j<<" : "<<to[i][j]<<endl;
}
}*/ scanf("%d",&T);
while(T--){
scanf("%I64d%I64d",&L,&R);
ansl=ansr=;
L--; if(L==) ansl=;
else{cnt=;
while(L){
a[++cnt]=L%;L/=;
}ansl=wrk();
} cnt=;
while(R){
a[++cnt]=R%;R/=;
}ansr=wrk(); printf("%I64d\n",ansr-ansl);
}
return ;
}

Beautiful numbers

[CQOI2016]手机号码

比较麻烦的数位dp

见另一片博客:

[CQOI2016]手机号码

[SCOI2014]方伯伯的商场之旅

非常麻烦的数位dp了。

见另外一篇博客:

[SCOI2014]方伯伯的商场之旅

数位dp——奏响数字数位的美妙乐章的更多相关文章

  1. 【数位DP】数字统计

    题目 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. 数位DP (1)分情况,逐位讨论. (2)模型:计算在[L,R]中有多少个数满足条件. (3)套路:将 ...

  2. 洛谷 - P2602 - 数字计数 - 数位dp

    https://www.luogu.org/problemnew/show/P2602 第二道数位dp,因为“数位dp都是模板题”(误),所以是从第一道的基础上面改的. 核心思想就是分类讨论,分不同情 ...

  3. 【学习笔记&训练记录】数位DP

    数位DP,即对数位进行拆分,利用数位来转移的一种DP,一般采用记忆化搜索,或者是先预处理再进行转移 一个比较大略的思想就是可以对于给定的大数,进行按数位进行固定来转移记录答案 区间类型的,可以考虑前缀 ...

  4. Codeforces Round #235 (Div. 2) D. Roman and Numbers (数位dp、状态压缩)

    D. Roman and Numbers time limit per test 4 seconds memory limit per test 512 megabytes input standar ...

  5. 数位dp 的简单入门

    时间紧张,就不讲那么详细了. 之前一直被深搜代码误解,以为数位dp 其实就是记忆化深搜...(虽说爆搜确实很舒服而且还好想) 但是后来发现数位dp 的标准格式其实是 预处理 + dp ...... 数 ...

  6. hdu6148 百度之星程序设计竞赛复赛 (数位dp)

    Valley Numer Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Tota ...

  7. 递推、数位DP解析(以HDU 2089 和 HDU 3555 为例)

    HDU 2089 不要62 题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=2089 Problem Description 杭州人称那些傻乎乎粘嗒嗒的人 ...

  8. 【hdu4507】吉哥系列故事——恨7不成妻 数位dp

    题目描述 求 $[L,R]$ 内满足:数位中不包含7.数位之和不是7的倍数.本身不是7的倍数 的所有数的平方和 mod $10^9+7$ . 输入 输入数据的第一行是case数T(1 <= T ...

  9. [hdu 2089] 不要62 数位dp|dfs 入门

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 题意:求[n, m]区间内不含4和62的数字个数. 这题有两种思路,直接数位dp和dfs 数位d ...

随机推荐

  1. 20155334 曹翔 Exp3 免杀原理与实践

    20155334 曹翔 Exp3 免杀原理与实践 小记:这次实验,困难重重,失败练练,搞得我们是心急如焚,焦头烂额,哭爹喊娘 一.基础问题回答 杀软是如何检测出恶意代码的? 每个杀软都有自己的检测库, ...

  2. WPF编程,通过Path类型制作沿路径运动的动画另一种方法。

    原文:WPF编程,通过Path类型制作沿路径运动的动画另一种方法. 版权声明:我不生产代码,我只是代码的搬运工. https://blog.csdn.net/qq_43307934/article/d ...

  3. Hadoop日记Day6---Linux的常用命令

    一.系统操作(开机.关机.登陆等)命令 选项名称 使用格式 含义 reboot 输入回车即可 立刻重启 shutdown shutdown –r   now 立刻重启 shutdown –r  20: ...

  4. 利用OVS+FLOODLIGHT,为数据表添加VLAN_ID和MPLS

    话不多说,直接上拓扑: 我这里是用主机h1 (10.0.0.1)ping 主机h2(10.0.0.2) 1.添加VLAN标签 v1: sudo ovs-ofctl add-flow m1-s1 in_ ...

  5. 蓝牙inquiry流程之Inquiry Complete处理

    inquiry流程一般持续有12s多,当inquiry完成的时候,设备端会上报一个Event: Inquiry Complete 上来,那协议栈是如何把这个事件上传到应用层的呢?本篇文章来分析一下其具 ...

  6. idea java方法中 传多个参数对象 的复制粘贴快速处理方法

    比如像这种的传多个参数对象,我是直接复制过来,然后把第一个字母改成大写,然后后面的实例对象敲一个第一个字符的小写,回车就直接出来了 在写调用参数的地方,ctrl+p 调出提示,然后按下提示里的实例的第 ...

  7. Android 模拟输入那点事

    因工作原因,需要用到模拟输入这个东东,查阅了一些资料,实现方式有多种,我大概分为两类,命令行类和程序类. 命令行类包括自动化测试组件monkeyrunner,getevent/setevent命令,i ...

  8. 蓝牙学习笔记二(Android连接问题)

    可以通过以下两点加速蓝牙连接: 1.更新连接参数 interval:连接间隔(connection intervals ),范围在 7.5 毫秒 到 4 秒. latency:连接延迟 ... 还有一 ...

  9. Unity2D 面向目标方向

    在2d空间上,假设角色的自身的y轴方向为正方向,如果要让角色随时面向一个目标点. 这里假设(0,0)点为目标点 第一种: Vector3 v = Vector3.zero - transform.po ...

  10. Daily Scrum 12.22

    姓名 上周末任务 今日任务 刘垚鹏 完善和增加quiz页面的过滤功能 完善和增加quiz页面的过滤功能 王骜 对问答功能的修复 对问答功能的修复 林旭鹏 存储文件路径太长导致bug修复 存储文件路径太 ...