数位dp相关

经典的数位Dp是要求统计符合限制的数字的个数。

一般的形式是:求区间[n,m]满足限制f(1)、 f(2)、 f(3)等等的数字的数量是多少。 条件 f(i) 一般与数的大小无关,而与数的组成有关。

善用不同进制来处理,一般问题都是10进制和二进制的数位dp。

数位dp的部分一般都是很套路的,但是有些题目在数位dp外面套了一个华丽的外衣,有时我们难以看出来。

直接就上的例题:

HDU3652

统计区间[1,n]中含有'13'且模13为0的数字有多少个。

N<=1e9;

 

咋做?不急,先从简化版的找规律;

HDU3652简化版

统计区间 [1,n] 中含有 '3' 的数字有多少个。

N=x_1 x_2 x_3 x_4….. x_total 。 x_i为n的从高到低第i位是多少。 Total是总的位数。

如果我们考虑从高到低位不断填数y_1 y_2 …。那么问题其实就是问有多少填数的方案,一要满足上限的限制(对应区间[1,n]),二要满足题目的其他限制。

这样其实就比[1,n]看起来更能dp了

假设到了第k位y_k!=x_k,则k位之后就没有上限的限制了,情况就简化了。

如果前面y中没有出现3:那么假如我们可以求出来, f[k][0]表示k位之后没有上限限制(随意填),但是必须填个3(前面没有出现),有多少种填数的方案。

如果前面y中出现了3:那么假如我们可以求出来, f[k][1]表示k位之后没有上限限制(随意填),没有必须出现3的限制(前面出现过了),有多少种填数的方案。

首先我们可以枚举到哪一位y_k!=x_k,然后再枚举这一位是多少,把对应的F加起来就是答案了,一共需要加 位数*10 次。这运算次数是不大的。

而f数组总大小也很小, 位数*2。

 

边界 f[total+1][0]=0,f[total+1][1]=1,转移复杂度O(10)

◦总复杂度

那回归到原题呢?

枚举哪一位不同没什么变化吧,跟原先一样枚举就好了。

就是f数组要变,因为约束条件更多了,所以状态的维数要增加。

设f[k][前面是否已经出现13][上一位是否是1][前面的那些数mod13等于多少],转移的话同样还是枚举这一位是填什么即可。

f[0][1][1/0][0]=1; else  =0;such as f[0][0][1/0][1~12]=0;

用记忆化搜索实现:

i表示到了第几位。

State:上一位是否为1。

Have:是否已经有13.

K:已经加上的数mod13的值。

注意只有不顶到上界才能记忆化下来答案。

remain[i]=1e(i-1)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>

using namespace std;

int cnt;
int s[50];
int t[12]={1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000,100000000000};
int f[15][10][2][13];

int dfs(int limits,int i,int num,bool have,int ret){
    if(i==0&&have&&ret==0) return 1;
    if(i<=0) return 0;
    if(!limits&&f[i][num][have][ret]!=-1) return f[i][num][have][ret];
    int up;
    if(limits) up=s[i];
    else up=9;
    int ans=0;
    for(int j=0;j<=up;j++){
        int h=(ret+t[i-1]*j)%13;
        ans+=dfs(limits&&j==up?1:0,i-1,j,have||(num==1&&j==3),h);
    }
    if(!limits) f[i][num][have][ret]=ans;
    return ans;
}

int main(){
    int n;
    while(scanf("%d",&n)==1){
        memset(s,0,sizeof(s));
        memset(f,-1,sizeof(f));
        cnt=0;
        while(n){
            s[++cnt]=n%10;
            n/=10;
        }
        cout<<dfs(1,cnt,0,0,0)<<endl;
    }
    return 0;
}
View Code

然后其他例题:1:统计区间 [m,n] 中4和7不能同时出现的数字有多少个?

2:给你一个区间[n,m],要你求区间内不含“62”或“4”的数字的个数?

3: bzoj1026: windy定义了一种windy数。 不含前导零且相邻两个数字

之差至少为2的正整数被称为windy数。 windy想知道,在A和B之间,包

括A和B,总共有多少个windy数?

hdu3079

题中平衡数的定义: 以一个位置作为平衡轴,然后左右其他数字本身大小作为重量,到平衡轴的距离作为权值,实现左右平衡(即杠杆原理平衡)。然后为区间[x,y]内平衡数的个数。 (0 ≤ x ≤ y ≤10^18 )

 单纯的,我们可以考虑到,对于任意非0数,如果有中心轴,它的中心轴必定只有一个。

我们以中心轴来进行数位dp即可;

bzoj3209

设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,花神要问你派(Sum(i)),也就是 sum(1)—sum(N) 的乘积。答案对一个质数取模。 对于 100% 的数据, N≤10^15

我们可以尝试枚举一个k,然后用数位dp求一共有多少个数有k个1,求出数量后显然ans*=k^数量,快速幂就可以了,但是要注意的一点是爆int;

bzoj4521: [Cqoi2016]手机号码

数字L到R中有多少个数字满足以下两个条件。

1:要出现至少3个相邻的相同数字

2:号码中不能同时出现8和4。

10^10 < = L < = R < 10^11

 dfs(i, same, last, appear, occur8, occur4, limit)

Same:上一位和上上一位是否相同

Last:上一位数字 Appear:连续三个相同是否出现过。

Occur4: 4是否出现过?

Occur8: 8是否出现过?

还用记前导0吗?不用

 

posted @ 2019-08-08 08:55  Sweetness  阅读(153)  评论(0编辑  收藏  举报