【一天一DP计划】数位dp

入坑之好博

一本通 数位DP

浅谈数位DP

## P4127 同类分布

现在关键的问题是:怎样记录dp状态?

这里 st可达到 1e18 显然是不能作为dp转移的下标直接记录的

所以我们考虑取模

我们最理想的模数当然是把每次搜到最后得到的数字各个位数之和

但是我们发现在这个过程中 sum是发生变化的

所以我们就应该以一个定值作为模数

那好,我们虽然不知道最后各位之和的结果,我们枚举总可以吧

我们只需要枚举所有的各位数字之和作为模数

最后判断 sum 和枚举的 mod相等并且 st%sum=0 的数就是符合题意的答案

/*
reference:
    https://www.luogu.org/blog/virus2017/p4127#
date:
    2019.10.03
solution:
    - 既然st的范围是1e18那么long long都存不下肯定不能记为dp的状态
        - 那么肯定要取模!模数为什么呢 ?最好为当前模数和sum
            - 可是sum在转移的时候一直在改变,那么之前记录的dp状态就无效了!
                - 那我们换个思路,模数呢最大为18*9,那我们枚举每个模数,判断当前在这个模数意义下是否合法
                    - 合法的条件:sum==mod && st==0(st要一直%mod)
*/
int a,b,len,mod;
int dp[18+5][18*9+5][18*9+5],bit[18+5];

inline int dfs(int pos,int sum,int st,int limit){
    if(pos>len && sum==0)return 0;
    if(pos>len)return sum==mod && st==0;
    if(!limit && dp[pos][sum][st]!=-1)return dp[pos][sum][st];
    int high=limit?bit[len-pos+1]:9;
    int res=0;
    rep(i,0,high){
        res+=dfs(pos+1,sum+i,(10ll*st+i)%mod,i==high && limit);
    }
//  return limit?res:dp[pos][sum][st]=res;
    if(!limit)dp[pos][sum][st]=res;
    return res;
}

inline int work(int x){
    len=0;
    while(x){
        bit[++len]=x%10;
        x/=10;
    }
    int res=0;
    for(mod=1;mod<=len*9;++mod){
        mem(dp,-1);
        res+=dfs(1,0,0,1);
    }
    return res;
}

#undef int
int main(){
#define int long long
    freopen("tonglei.txt","r",stdin);
    rd(a),rd(b);
    printf("%lld",work(b)-work(a-1));
    return 0;
}

##windy数

/*
reference:
    https://www.luogu.org/blog/virus2017/solution-p2657
date:
    2019.10.03
solution:
    最妙:考虑前导零的时候,第一位的时候,0和1是没办法取到的,因为abs(i-pre)<2了,
    那么,就等价于,pre设为-2问题解决
*/
int a,b,len;
int bit[15],dp[15][15];

inline int dfs(int pos,int pre,int lead,int limit){
    if(pos>len)return 1;
    if(!limit && dp[pos][pre]!=-1)return dp[pos][pre];
    int high=limit?bit[len-pos+1]:9;
    int res=0;
    rep(i,0,high){
        if(abs(i-pre)<2)continue;
        else if(lead && i==0)
            res+=dfs(pos+1,-2,1,i==high && limit);
        else
            res+=dfs(pos+1,i,0,i==high && limit);
    }
    if(!limit && !lead)dp[pos][pre]=res;
    return res;
}

inline int work(int x){
    len=0;
    while(x){
        bit[++len]=x%10;
        x/=10;
    }
    mem(dp,-1);
    return dfs(1,-2,1,1);
}

#undef int
int main(){
#define int long long
    #ifdef WIN64
    freopen("windy.txt","r",stdin);
    #endif
    rd(a),rd(b);
    printf("%lld\n",work(b)-work(a-1));
    return 0;
}

##数字计数(求不降数)

/*
int a,b,len,mod;
int bit[15],dp[15][15];

inline int dfs(int pos,int pre,int limit){
    if(pos>len)return 1;
    if(dp[pos][pre]!=-1 && !limit)return dp[pos][pre];
    int high=limit?bit[len-pos+1]:9;
    int res=0;
    rep(i,pre,high){
        res+=dfs(pos+1,i,i==high && limit);
    }
    if(!limit)dp[pos][pre]=res;
    return res;
}

inline int work(int x){
    len=0;
    while(x){
        bit[++len]=x%10;
        x/=10;
    }
    mem(dp,-1);
    return dfs(1,0,1);
}
#undef int
int main(){
#define int long long
    #ifdef WIN64
    freopen("shuzi2.txt","r",stdin);
    #endif
    while(~scanf("%lld%lld",&a,&b)){
        printf("%lld\n",work(b)-work(a-1));
    }
    return 0;
}

##不要62

/*
int a,b,len;
int bit[10],dp[10][15];

inline int dfs(int pos,int pre,int limit){
    if(pos>len)return 1;
    if(dp[pos][pre]!=-1 && !limit)return dp[pos][pre];
    int high=limit?bit[len-pos+1]:9;
    int res=0;
    rep(i,0,high){
        if(i==4)continue;
        if(pre==6 && i==2)continue;
        res+=dfs(pos+1,i,i==high && limit);
    }
    if(!limit)dp[pos][pre]=res;
    return res;
}

inline int work(int x){
    mem(bit,0);
    len=0;
    while(x){
        bit[++len]=x%10;
        x/=10;
    }
    mem(dp,-1);
    return dfs(1,0,1);
}

#undef int
int main(){
#define int long long
    #ifdef WIN64
    freopen("62.txt","r",stdin);
    #endif
    while(1){
        rd(a),rd(b);
        if(a==0 && b==0)break;
        printf("%lld\n",work(b)-work(a-1));
    }
    return 0;
}

##数字游戏(取模)

int a,b,len,mod;
int bit[15],dp[15][900];

inline int dfs(int pos,int sum,int limit){
    if(pos>len)return sum==0?1:0;
    if(!limit && dp[pos][sum]!=-1)return dp[pos][sum];
    int high=limit?bit[len-pos+1]:9;
    int res=0;
    rep(i,0,high){
        res+=dfs(pos+1,(sum+i)%mod,i==high && limit);
    }
    if(!limit)dp[pos][sum]=res;
    return res;
}

inline int work(int x){
    len=0;
    while(x){
        bit[++len]=x%10;
        x/=10;
    }
    mem(dp,-1);
    return dfs(1,0,1);
}

#undef int
int main(){
#define int long long
    #ifdef WIN64
    freopen("shuzi.txt","r",stdin);
    #endif
    while(~scanf("%lld%lld%lld",&a,&b,&mod)){
        printf("%lld\n",work(b)-work(a-1));
    }
    return 0;
}

原文地址:https://www.cnblogs.com/sjsjsj-minus-Si/p/11634654.html

时间: 2024-10-29 00:06:23

【一天一DP计划】数位dp的相关文章

BestCoder Round #75 King&amp;#39;s Order dp:数位dp

King's Order Accepts: 381 Submissions: 1361 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Problem Description After the king's speech , everyone is encouraged. But the war is not over. The king needs to give orders

数位dp初步——数位dp的两种方式

数位dp:一类统计区间[L,R]内某种符合规定的数字个数的题目.特征是R的范围会很大,O(N)范围内无法完成. 一般而言,解决这类题目有两种方式,一种是递推,另一种是记忆化搜索. 递推: 1)利用dp求出数组f[i][j](表示有i位,最高位为j的数字中符合要求的数字的个数) 2)根据给出的[L,R],利用f[]统计答案 记忆化搜索: 1 int dfs(int pos,int pre,int lim){//当前位置 前一位的数字 是否有限制 2 if(pos<=0)return 1; 3 if

HDU 3555 Bomb (数位DP)

数位dp,主要用来解决统计满足某类特殊关系或有某些特点的区间内的数的个数,它是按位来进行计数统计的,可以保存子状态,速度较快.数位dp做多了后,套路基本上都差不多,关键把要保存的状态给抽象出来,保存下来. 简介: 顾名思义,所谓的数位DP就是按照数字的个,十,百,千--位数进行的DP.数位DP的题目有着非常明显的性质: 询问[l,r]的区间内,有多少的数字满足某个性质 做法根据前缀和的思想,求出[0,l-1]和[0,r]中满足性质的数的个数,然后相减即可. 算法核心: 关于数位DP,貌似写法还是

数位dp模版(dp)

1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <algorithm> 5 6 using namespace std; 7 8 int t; 9 long long dp[19][19][2005]; 10 long long l, r; 11 int shu[20]; 12 13 long long dfs(int len,..., bool shangx

Codeforces Round #460 (Div. 2) B Perfect Number(二分+数位dp)

题目传送门 B. Perfect Number time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output We consider a positive integer perfect, if and only if the sum of its digits is exactly 1010. Given a positive integ

[模板] 数位dp

数位dp 简介 数位dp指满足特定性质的数的计数, 如求 \([l, r]\) 区间内不含 \(2\) 的数的个数. 一般来说, 数位dp利用dfs解决, 有时状态数较多, 需要hash表优化. 模板: // 求[l,r] 中各位数字之积为特定值(prod[])数的个数 ll dp[nsz][35][25][15][15]; ll dfs(int p,ll v,ll base,ll l,ll r){ ll maxv=v+base-1; if(maxv<l||v>r)return 0; if(p

数位dp小练

最近刷题的同时还得填填坑,说来你们也不信,我还不会数位dp. 照例推几篇博客: 数位DP讲解 数位dp 的简单入门 这两篇博客讲的都很好,不过代码推荐记搜的形式,不仅易于理解,还短. 数位dp的式子一般是这样的:dp[i][][]表示到第\(i\)位,而后面几维就因题而异了. 不过通用的思想就是利用前缀相减求出区间信息. 算了上题吧. [SCOI2009]windy数 这都说是数位dp入门题. 根据这题,受到影响的数只有相邻两个,因此dp[i][j]表示到第\(i\)位(从高往低)上一位的数\(

51Nod 1009 数字1的个数 | 数位DP

题意: 小于等于n的所有数中1的出现次数 分析: 数位DP 预处理dp[i][j]存 从1~以j开头的i位数中有几个1,那么转移方程为: if(j == 1) dp[i][j] = dp[i-1][9]*2+pow(10,i-1);else dp[i][j] = dp[i-1][9]+dp[i][j-1]; 然后注意下对于每个询问统计的时候如果当前位为1需要额外加上他后面所有位数的个数,就是n%pow(10,i-1); 这样总复杂度log(n)*10 #include <bits/stdc++.

51nod1043(数位dp)

题目链接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1043 题意:中文题诶- 思路:数位dp 我们用dp[i][j]来存储长度为2*i且一半和为j的所有情况(包括前导0的情况),为了方便我们现在只讨论其一半的和的情况,因为如果包括前导0的话其两边的情况是一样的: 我们假设再长度为i-1的数字最前面加1位数字k,0<=k<=9(这位数字加在哪里并不影响答案,因为我们在计算i-1长度的时候已经计算了所有组合情况,