[kuangbin带你飞]专题十五 数位DP

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 



62 / 175 Problem A CodeForces 55D Beautiful numbers

一个美丽数就是可以被它的每一位的数字整除的数。

给定一个区间,求美丽数的个数。

这题是很好的数位DP。

比较难想状态。

就是被每一个数字的LCM整除。

1~9的LCM最大是2520,其实也只有48个。

然后dp[i][j][k]表示处理到数位i,该数对2520取模为j,各个数位的LCM为k

/*
 * 题意:求区间[x , y]中beautiful number的个数,
 * a positive integer number is beautiful if and only
 * if it is divisible by each of its nonzero digits.
分析:一个数能被它的所有非零数位整除,则能被它们的最小公倍数整除,而1到9的最小公倍数为2520,
数位DP时我们只需保存前面那些位的最小公倍数就可进行状态转移,到边界时就把所有位的lcm求出了,
为了判断这个数能否被它的所有数位整除,我们还需要这个数的值,显然要记录值是不可能的,其实我们只
需记录它对2520的模即可,这样我们就可以设计出如下数位DP:dfs(pos,mod,lcm,f),pos为当前
位,mod为前面那些位对2520的模,lcm为前面那些数位的最小公倍数,f标记前面那些位是否达到上限,
这样一来dp数组就要开到19*2520*2520,明显超内存了,考虑到最小公倍数是离散的,1-2520中可能
是最小公倍数的其实只有48个,经过离散化处理后,dp数组的最后一维可以降到48,这样就不会超了。
 */

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int MAXN=25;
const int MOD=2520;//1~9的lcm为2520
long long dp[MAXN][MOD][48];
int index[MOD+10];//记录1~9的最小公倍数
int bit[MAXN];
int gcd(int a,int b)
{
    if(b==0)return a;
    else return gcd(b,a%b);
}
int lcm(int a,int b)
{
    return a/gcd(a,b)*b;
}

void init()
{
    int num=0;
    for(int i=1;i<=MOD;i++)
        if(MOD%i==0)
            index[i]=num++;
}
long long dfs(int pos,int preSum,int preLcm,bool flag)
{
    if(pos==-1)
        return preSum%preLcm==0;
    if(!flag && dp[pos][preSum][index[preLcm]]!=-1)
        return dp[pos][preSum][index[preLcm]];
    long long ans=0;
    int end=flag?bit[pos]:9;//上界
    for(int i=0;i<=end;i++)
    {
        int nowSum=(preSum*10+i)%MOD;
        int nowLcm=preLcm;
        if(i)nowLcm=lcm(nowLcm,i);
        ans+=dfs(pos-1,nowSum,nowLcm,flag && i==end);
    }
    if(!flag)dp[pos][preSum][index[preLcm]]=ans;
    return ans;
}
long long calc(long long x)
{
    int pos=0;
    while(x)
    {
        bit[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1,1);
}
int main()
{
    int T;
    long long l,r;
    init();
    memset(dp,-1,sizeof(dp));
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d",&l,&r);
        printf("%I64d
",calc(r)-calc(l-1));
    }
    return 0;
}
View Code

30 / 84 Problem B HDU 4352 XHXJ's LIS


108 / 195 Problem C HDU 2089 不要62

d.求n~m间的数中,多少不带4和62的数。

n、m(0<n≤m<1000000)

s.见注释

#include<iostream>
#include<stdio.h>
using namespace std;

long long dp[10][3];
/*
i位数时,相应情况的数字总数
dp[i][0],不含有不吉利数字
dp[i][1],不含有不吉利数字,最高位为2
dp[i][2],含有不吉利数字
*/
void init(){
    dp[0][0]=1;
    dp[0][1]=dp[0][2]=0;
    int i;
    for(i=1;i<=6;++i){
        dp[i][0]=9*dp[i-1][0]-dp[i-1][1];//不含不吉利数字前面加除4外9个数,减掉2前面加6
        dp[i][1]=dp[i-1][0];//不含不吉利数字前面加2
        dp[i][2]=10*dp[i-1][2]+dp[i-1][0]+dp[i-1][1];//含不吉利数字前面加0~9,加上不含不吉利数字前加4,加上2前面加6
    }
}
int bit[10];
int calc(int n){
    int len=0,i,tmp=n;
    while(n){
        bit[++len]=n%10;
        n/=10;
    }
    bit[len+1]=0;
    bool flag=false;//前缀出现4或者62了吗
    long long ans=0;
    for(i=len;i>=1;--i){
        ans+=dp[i-1][2]*bit[i];//注意这个位置,求的是前缀+当前位置(0~bit[i]-1正好是bit[i]种,当前位是不能为bit[i]的)
        if(flag)ans+=dp[i-1][0]*bit[i];//此时当前位为0~bit[i]-1时,后面不含不吉利数字的全都不行
        else{//有3种情况
            if(bit[i]>4)ans+=dp[i-1][0];//这位为4的时候(ps:为什么不是>=4?因为只有>4时才能保证当前位取4时,后面的数全都能取到,下同)
            if(bit[i+1]==6&&bit[i]>2)ans+=dp[i][1];//上位为6,这位为2的时候
            if(bit[i]>6)ans+=dp[i-1][1];//这位为6的时候
        }
        if(bit[i]==4||bit[i+1]==6&&bit[i]==2)flag=true;//前缀出现的4或者62了。
    }
    if(flag)++ans;//加上n本身,这个数也不行
    return tmp-ans;
}

int main(){
    init();
    int n,m;

    while(scanf("%d%d",&n,&m)){
        if(n==0&&m==0)break;
        printf("%d
",calc(m)-calc(n-1));
    }
    return 0;
}
View Code

89 / 222 Problem D HDU 3555 Bomb

d.求1到n有多少个数中含有49,1<=n<=2^63-1(2^32是10位,2^64约20位)

s.数位dp,和上个题类似,少了个条件

dp[i][0],长度为i,不含有49的个数
dp[i][1],长度为i,不含有49,最高位为9的个数
dp[i][2],长度为i,含有49的个数

状态转移方程:

dp[i][0]=10*dp[i-1][0]-dp[i-1][1];//不含49的前面加0~9,减掉9前面加4
dp[i][1]=dp[i-1][0];//不含49的前面加9
dp[i][2]=10*dp[i-1][2]+dp[i-1][1];//含49的前面加0~9,加上9前面加4

求解:从最高位开始遍历,每一位求的都是  前缀+小于当前位  的符合条件的个数。

1.首先加上当前位置之后包含49的个数,因为当前为可以填0~bit[i]-1,所以乘以bit[i],ans+=dp[i-1][2]*bit[i];//注意这个位置,求的是前缀+当前位置
2.前面的前缀出现49了,ans+=dp[i-1][0]*bit[i];//
3.前缀没有出现49,并且当前位>4,ans+=dp[i-1][1];

#include<iostream>
#include<stdio.h>
using namespace std;

long long dp[25][3];
/*
dp[i][0],不含有49
dp[i][1],不含有49,最高位为9
dp[i][2],含有49
*/
void init(){
    dp[0][0]=1;
    dp[0][1]=dp[0][2]=0;
    int i;
    for(i=1;i<25;++i){
        dp[i][0]=10*dp[i-1][0]-dp[i-1][1];//不含49的前面加0~9,减掉9前面加4
        dp[i][1]=dp[i-1][0];//不含49的前面加9
        dp[i][2]=10*dp[i-1][2]+dp[i-1][1];//含49的前面加0~9,加上9前面加4
    }
}
int bit[25];
long long calc(long long n){
    int len=0,i;
    while(n){
        bit[++len]=n%10;
        n/=10;
    }
    bit[len+1]=0;
    bool flag=false;
    long long ans=0;
    for(i=len;i>=1;--i){
        ans+=dp[i-1][2]*bit[i];//注意这个位置,求的是前缀+当前位置
        if(flag)ans+=dp[i-1][0]*bit[i];
        else if(bit[i]>4)ans+=dp[i-1][1];
        if(bit[i+1]==4&&bit[i]==9)flag=true;
    }
    if(flag)++ans;//加上n本身
    return ans;
}

int main(){
    init();
    int t;
    long long n;
    scanf("%d",&t);
    while(t--){
        scanf("%lld",&n);
        printf("%I64d
",calc(n));
    }
    return 0;
}
View Code

59 / 107 Problem E POJ 3252 Round Numbers

d.Round Numbers 就是一个表示成二进制的时候0比1多或者相等的正数,注意是正数,所以0就肯定不是了。
题目是给定一个区间,问在这个区间上的Round Numbers有多少个?
首先是求出小于等于n的Round Numbers有多少个。
s.我先举个例子来先说明,再来说一般方法。
比如: 22 = 10110b 如果要求 <=22的Round Numbers,也就是找出1-22有多少个二进制的0不少于1的数的个数。
22的二进制长度是5.
首先找长度比5小的Round Numbers(长度比5小的数肯定小于22啦)
长度为4的话,第一位必须是1,后面三位的话,可以有2个0,3个0
所以就是C(3,2)+C(3,3);
长度为3的Round Numbers,同理有 C(2,2);//注意不要把第一位1忘记了
长度为2的Round Numbers,有 C(1,1)
长度为1的Round Numbers,有 0个
下面是找长度和22相同的Round Numbers。
首先第一位是1.
22的第二位是0,所以第二位不能为1,必须是0
第三位为0的话,(前面有了2个0,1个1),后面两位可以有1个0,2个0
C(2,1)+C(2,2)
接下来把第三位恢复为1,看第四位。假如第四位是0,(前面有2个0,2个1),后面一位必须是0 C(1,1)
所以大致求的过程就如上面所述。


首先先推个公式,就是长度为len的Round Numbers的个数。
长度为len,第一位肯定是1了。
那么后面剩下 len-1位。
如果len-1是偶数。
那么 C(len-1,(len-1)/2+1)+C(len-1,(len-1)/2+2)+````C(len-1,len-1)
= ( 2^(len-1)-C(len-1,(len-1)/2) )/2;
如果len是奇数
那么就是 ( 2^(len-1) )/2
所以上面求比N长度小的Round Numbers是很好求的了。
至于求长度的,则是逐渐把每一位1变为0,去求后面的,就可以保证比n小了。
看代码吧。很容易理解的。

#include<iostream>
#include<stdio.h>
using namespace std;

int C[33][33];
void init(){
    C[0][0]=1;
    C[1][0]=1;C[1][1]=1;
    int i,j;
    for(i=2;i<33;++i){
        C[i][0]=1;
        for(j=1;j<i;++j){
            C[i][j]=C[i-1][j-1]+C[i-1][j];
        }
        C[i][i]=1;
    }
}
int bits[33];
int calc(int n){////求小于等于n的 Round Numbers
    if(n==0){//
        return 0;
    }
    int len;
    len=0;
    while(n>0){
        if(n&1){
            bits[len++]=1;
        }
        else{
            bits[len++]=0;
        }
        n>>=1;
    }
    int ans;
    ans=0;
    int i;
    //求出长度1~len-1的所有情况
    for(i=len-1;i>0;--i){
        if(i&1){
            ans=ans+((1<<(i-1))-C[i-1][(i-1)/2])/2;
        }
        else{
            ans=ans+(1<<(i-1))/2;
        }
    }
    int cnt0,cnt1;//前缀出现0的个数和1的个数
    cnt0=0;
    cnt1=1;
    int j;
    //长度为len的情况,小于n的
    for(i=len-2;i>=0;--i){
        if(bits[i]==1){//后面有i位,当前第i位当成0
            for(j=i;j>=0&&cnt0+1+j>=cnt1+i-j;--j){//后面i位中选j个0
                ans=ans+C[i][j];
            }
            ++cnt1;
        }
        else{
            ++cnt0;
        }
    }
    //看看原来这个数n
    cnt0=0;
    cnt1=0;
    for(i=0;i<len;++i){
        if(bits[i]==0){
            ++cnt0;
        }
        else{
            ++cnt1;
        }
    }
    if(cnt0>=cnt1){
        ++ans;
    }
    return ans;
}

int main(){
    init();
    int a,b;

    while(~scanf("%d%d",&a,&b)){
        printf("%d
",calc(b)-calc(a-1));
    }

    return 0;
}
View Code


47 / 75 Problem F HDU 3709 Balanced Number

找出区间内平衡数的个数,所谓的平衡数,就是以这个数字的某一位为支点,另外两边的数字大小乘以力矩之和相等,即为平衡数

/*
 * HDU 3709
 * 平衡数,枚举支点
 * dp[i][j][k] i表示处理到的数位,j是支点,k是力矩和
 */

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
long long dp[20][20][2000];
int bit[20];
long long dfs(int pos,int center,int pre,bool flag)
{
    if(pos==-1)return pre==0;
    if(pre<0)return 0;//当前力矩为负,剪枝
    if(!flag&&dp[pos][center][pre]!=-1)
        return dp[pos][center][pre];
    int end=flag?bit[pos]:9;
    long long ans=0;
    for(int i=0;i<=end;i++)
        ans+=dfs(pos-1,center,pre+i*(pos-center),flag&&i==end);
    if(!flag)dp[pos][center][pre]=ans;
    return ans;
}
long long calc(long long n)
{
    int len=0;
    while(n)
    {
        bit[len++]=n%10;
        n/=10;
    }
    long long ans=0;
    for(int i=0;i<len;i++)
        ans+=dfs(len-1,i,0,1);
    return ans-(len-1);//去掉全0的情况
}
int main()
{
//    freopen("in.txt","r",stdin);
//    freopen("out.txt","w",stdout);
    int T;
    long long x,y;
    memset(dp,-1,sizeof(dp));//这个初始化一定别忘记
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d",&x,&y);
        printf("%I64d
",calc(y)-calc(x-1));
    }
    return 0;
}
View Code

64 / 95 Problem G HDU 3652 B-number

找出1~n范围内含有13并且能被13整除的数字的个数

/*
 * HDU 3652 B-number
 * 含有数字13和能够被13整除的数的个数
 * dp[i][j][k][z]:i:处理的数位,j:该数对13取模以后的值,k:是否已经包含13,z结尾的数
 */
#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
using namespace std;
int dp[12][15][2][10];
int bit[12];
int dfs(int pos,int num,bool t,int e,bool flag)
{
    if(pos==-1)return t&&(num==0);
    if(!flag && dp[pos][num][t][e]!=-1)
        return dp[pos][num][t][e];
    int end=flag?bit[pos]:9;
    int ans=0;
    for(int i=0;i<=end;i++)
        ans+=dfs(pos-1,(num*10+i)%13,t||(e==1&&i==3),i,flag&&(i==end));
    if(!flag)dp[pos][num][t][e]=ans;
    return ans;
}
int calc(int n)
{
    int pos=0;
    while(n)
    {
        bit[pos++]=n%10;
        n/=10;
    }
    return dfs(pos-1,0,0,0,1);
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int n;
    memset(dp,-1,sizeof(dp));
    while(scanf("%d",&n)==1)
    {
        printf("%d
",calc(n));
    }
    return 0;
}
View Code

42 / 124 Problem H HDU 4734 F(x)

定义十进制数x的权值为f(x) = a(n)*2^(n-1)+a(n-1)*2(n-2)+...a(2)*2+a(1)*1,a(i)表示十进制数x中第i位的数字。

题目给出a,b,求出0~b有多少个权值不大于f(a)的数。

dp[i][j]表示i位值<=j 的总数

/* ***********************************************
Author        :kuangbin
Created Time  :2013/9/14 星期六 12:45:42
File Name     :2013成都网络赛1007.cpp
************************************************ */

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;

int dp[20][200000];

int bit[20];



int dfs(int pos,int num,bool flag)
{
    if(pos == -1)return num >= 0;
    if(num < 0)return 0;
    if(!flag && dp[pos][num] != -1)
        return dp[pos][num];
    int ans = 0;
    int end = flag?bit[pos]:9;
    for(int i = 0;i <= end;i++)
    {

        ans += dfs(pos-1,num - i*(1<<pos),flag && i==end);
    }
    if(!flag)dp[pos][num] = ans;
    return ans;
}

int F(int x)
{
    int ret = 0;
    int len = 0;
    while(x)
    {
        ret += (x%10)*(1<<len);
        len++;
        x /= 10;
    }
    return ret;
}
int A,B;
int calc()
{
    int len = 0;
    while(B)
    {
        bit[len++] = B%10;
        B/=10;
        //cout<<bit[len-1]<<endl;
    }
    //cout<<F(A)<<endl;
    return dfs(len-1,F(A),1);
}




int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int T;
    int iCase = 0;
    scanf("%d",&T);
    memset(dp,-1,sizeof(dp));
    while(T--)
    {
        iCase++;
        scanf("%d%d",&A,&B);
        printf("Case #%d: %d
",iCase,calc());
    }
    return 0;
}
View Code

11 / 20 Problem I ZOJ 3494 BCD Code
31 / 100 Problem J HDU 4507 吉哥系列故事――恨7不成妻

如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
  1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍;

要求一个区间中和7无关的数的平方和。

需要用数位DP维护3个值:

1.与7无关的数的个数

2.与7无关的数的和

3、与7无关的数的平方和。

第一个是与7无关的数的个数,就是简单的数位DP了,很常规。

第二个与7无关的数的和的维护需要用到第一个个数。

处理到第pos个数位时,加上i*10^pos * 后面的个数

第三个的维护需要用到前面两个

(pre*10^pos + next)^2= (pre*10^pos)^2+2*pre*10^pos*next +next^2

/*
 *  如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
  1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍;

求一个区间中与7无关的数的平方和
 */
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const long long MOD=1000000007LL;
struct Node
{
    long long cnt;//与7无关的数的个数
    long long sum;//与7无关的数的和
    long long sqsum;//平方和
}dp[20][10][10];//分别是处理的数位、数字和%7,数%7
int bit[20];
long long p[20];//p[i]=10^i

Node dfs(int pos,int pre1,int pre2,bool flag)
{
    if(pos==-1)
    {
        Node tmp;
        tmp.cnt=(pre1!=0 && pre2!=0);
        tmp.sum=tmp.sqsum=0;
        return tmp;
    }
    if(!flag && dp[pos][pre1][pre2].cnt!=-1)
        return dp[pos][pre1][pre2];
    int end=flag?bit[pos]:9;
    Node ans;
    Node tmp;
    ans.cnt=ans.sqsum=ans.sum=0;
    for(int i=0;i<=end;i++)
    {
        if(i==7)continue;
        tmp=dfs(pos-1,(pre1+i)%7,(pre2*10+i)%7,flag&&i==end);
        ans.cnt+=tmp.cnt;
        ans.cnt%=MOD;
        ans.sum+=(tmp.sum+ ((p[pos]*i)%MOD)*tmp.cnt%MOD )%MOD;
        ans.sum%=MOD;

        ans.sqsum+=(tmp.sqsum + ( (2*p[pos]*i)%MOD )*tmp.sum)%MOD;
        ans.sqsum%=MOD;
        ans.sqsum+=( (tmp.cnt*p[pos])%MOD*p[pos]%MOD*i*i%MOD );
        ans.sqsum%=MOD;
    }
    if(!flag)dp[pos][pre1][pre2]=ans;
    return ans;
}
long long calc(long long n)
{
    int pos=0;
    while(n)
    {
        bit[pos++]=n%10;
        n/=10;
    }
    return dfs(pos-1,0,0,1).sqsum;
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int T;
    long long l,r;
    p[0]=1;
    for(int i=1;i<20;i++)
        p[i]=(p[i-1]*10)%MOD;
    for(int i=0;i<20;i++)
        for(int j=0;j<10;j++)
            for(int k=0;k<10;k++)
                dp[i][j][k].cnt=-1;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d",&l,&r);
        long long ans=calc(r);
        ans-=calc(l-1);
        ans=(ans%MOD+MOD)%MOD;
        printf("%I64d
",ans);
    }
    return 0;
}
View Code

42 / 75 Problem K SPOJ BALNUM Balanced Numbers

这题要求出现的数字,偶数出现奇数次,奇数出现偶数次。

用三进制表示0~9的状态

//============================================================================
// Name        : SPOJ.cpp
// Author      : 
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
long long dp[20][60000];
//3进制表示数字0~9的出现情况,0表示没有出现,1表示奇数次,2表示偶数次
int bit[20];
bool check(int s)
{
    int num[10];
    for(int i=0;i<10;i++)
    {
        num[i]=s%3;
        s/=3;
    }
    for(int i=0;i<10;i++)
        if(num[i]!=0)
        {
            if(i%2==0 && num[i]==2)return false;
            if(i%2==1 && num[i]==1)return false;
        }
    return true;
}
int getnews(int x,int s)
{
    int num[10];
    for(int i=0;i<10;i++)
    {
        num[i]=s%3;
        s/=3;
    }
    if(num[x]==0)num[x]=1;
    else num[x]=3-num[x];
    int news=0;
    for(int i=9;i>=0;i--)
    {
        news*=3;
        news+=num[i];
    }
    return news;
}
long long dfs(int pos,int s,bool flag,bool z)
{
    if(pos==-1)return check(s);
    if(!flag && dp[pos][s]!=-1)
        return dp[pos][s];
    long long ans=0;
    int end=flag?bit[pos]:9;
    for(int i=0;i<=end;i++)
        ans+=dfs(pos-1,(z&&i==0)?0:getnews(i,s),flag&&i==end,z&&i==0);
    if(!flag)dp[pos][s]=ans;
    return ans;
}
long long calc(long long n)
{
    int len=0;
    while(n)
    {
        bit[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,0,1,1);
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int T;
    memset(dp,-1,sizeof(dp));
    long long a,b;
    scanf("%d",&T);
    while(T--)
    {
        cin>>a>>b;
        cout<<calc(b)-calc(a-1)<<endl;
    }
    return 0;
}
View Code

ps:从这个题发现了pow的精度问题,呵呵

http://blog.csdn.net/u014665013/article/details/70990408

原文地址:https://www.cnblogs.com/gongpixin/p/5360681.html