hdu 5787 数位dp,记忆化搜索

题意:求区间[l,r]内有多少个数符合,这个数的任意的相邻k位数(digits),这k个数都两两不相等

l,r范围是1~1e18,k是2~5

思路:数位DP,因为K<=5,我们最多需要保存下来当前位的前4位就足够了。因为dp[pos][p1][p2][p3][p4]表示,现在枚举取第pos位,pos位之前的四位分别为p1,p2,p3,p4,p4是pos的上一位。那么p1~p4的范围就是0~9,但是因为总位数小于当前数字的位数的数也要进行枚举,需要一个数字来区分它是前导0还是在中间时为0,令p = 10是前导0,或者表示不需要储存,即最高位的前几位。那么dfs函数可以写成 dfs( int pos , int p1 , int p2 , int p3 , int p4 , bool flag ) flag表示pos位能否取到全部的数位(0~9)还是会受到前面最高位的影响只能取一部分。

对于记忆化搜索首先明确代码的dfs的定义为当前四位分别为p1,p2,p3,p4时枚举第pos位总共有多少种方法,则dp方程很容易便能想到是1、pos位之前都取的0,即p4==10,而pos位也取0,一直都是前导零。2、而当p4!=10,pos位取的i去和p判断一下有没有重复(根据k来判断需要比较几个p),所以也是可以向下统计的,然后记得记忆化就好。

而dp的定义为当前四位数为p1,p2,p3,p4是枚举pos位总共有多少种方法,这与dfs是不同的,因为对于dp 不需要考虑必须在小于当前数的范围内进行枚举,而dfs代表的方法数必须在小于当前数的范围之内,这就可以解释第38行。同时因为大多数情况下都是flag为假,随意选择记忆这种情况可以降低复杂度(这是我猜的,如果大家有别的想法可以说出来交流一下)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <math.h>
#include <algorithm>
#include <queue>
#include <map>
using namespace std;
typedef long long ll;
const int N = 50000+10;
const int mod=1e9+7;
const double en=2.718281828459;
ll l,r,dp[20][11][11][11][11];
int k,dig[20];
bool cek(int u,int p1,int p2,int p3,int p4){
    if(k==2)
        return u!=p4;
    if(k==3)
        return u!=p4&&u!=p3;
    if(k==4)
        return u!=p4&&u!=p3&&u!=p2;
    if(k==5)
        return u!=p4&&u!=p3&&u!=p2&&u!=p1;
}
ll dfs(int pos,int p1,int p2,int p3,int p4,int flag){
if(pos==0)
    return p4!=10;
if(!flag&&dp[pos][p1][p2][p3][p4]!=-1) return dp[pos][p1][p2][p3][p4];//记忆化
int ed=flag?dig[pos]:9;//当前面的数取小于其位置上的数显然后面的数可以取0~9
ll ans=0;
for(int i=0;i<=ed;i++){
    if(i==0&&p4==10)
        ans+=dfs(pos-1,10,10,10,10,flag&&i==ed);
    else if(cek(i,p1,p2,p3,p4))
        ans+=dfs(pos-1,p2,p3,p4,i,flag&&i==ed);

}
   if(!flag) dp[pos][p1][p2][p3][p4]=ans;
   return ans;
}
ll cal(ll u){
int cnt=0;
while(u>0){
    dig[++cnt]=u%10;
    u/=10;
}
return dfs(cnt,10,10,10,10,1);
}
int main()
{

    //freopen("in.txt", "r", stdin);
    while(~scanf("%lld %lld %d",&l,&r,&k)){
        memset(dp,-1,sizeof(dp));
        printf("%lld\n",cal(r)-cal(l-1));
    }
    return 0;
}
时间: 2024-12-04 19:27:39

hdu 5787 数位dp,记忆化搜索的相关文章

[hihocoder 1033]交错和 数位dp/记忆化搜索

#1033 : 交错和 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一个数 x,设它十进制展从高位到低位上的数位依次是 a0,?a1,?...,?an?-?1,定义交错和函数: f(x)?=?a0?-?a1?+?a2?-?...?+?(?-?1)n?-?1an?-?1 例如: f(3214567)?=?3?-?2?+?1?-?4?+?5?-?6?+?7?=?4 给定 输入 输入数据仅一行包含三个整数,l,?r,?k(0?≤?l?≤?r?≤?1018,?|k|

HDU 2089 不要62(数位dp&amp;记忆化搜索)

题目链接:[kuangbin带你飞]专题十五 数位DP C - 不要62 题意 杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer). 杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众. 不吉利的数字为所有含有4或62的号码.例如: 62315 73418 88914 都属于不吉利号码.但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列. 你的任务是,对于每次给

HDU 3652 B-number(数位dp&amp;记忆化搜索)

题目链接:[kuangbin带你飞]专题十五 数位DP G - B-number 题意 求1-n的范围里含有13且能被13整除的数字的个数. 思路 首先,了解这样一个式子:a%m == ((b%m)*c+d)%m; 式子的正确是显然的,就不证明了. 那么判断数是否可以被13整除就可以分解为一位一位进行处理. 当然,我们也只需要储存取余后的值. dfs(len, num, mod, flag) mod记录数字对13取余后的值 len表示当前位数 num==0 不含13且上一位不为1 pre==1

数位dp/记忆化搜索

一.引例 #1033 : 交错和 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 给定一个数 x,设它十进制展从高位到低位上的数位依次是 a0, a1, ..., an - 1,定义交错和函数: f(x) = a0 - a1 + a2 - ... + ( - 1)n - 1an - 1 例如: f(3214567) = 3 - 2 + 1 - 4 + 5 - 6 + 7 = 4 给定 l, r, k,求在 [l, r] 区间中,所有 f(x) = k 的 x 的和,

bzoj1833: [ZJOI2010]count 数字计数(数位DP+记忆化搜索)

1833: [ZJOI2010]count 数字计数 题目:传送门 题解: 今天是躲不开各种恶心DP了??? %爆靖大佬啊!!! 据说是数位DP裸题...emmm学吧学吧 感觉记忆化搜索特别强: 定义f[i][j][k]表示若前i个位置有k个j的此时的全局方案数,然后就可以记忆化搜索了(具体看代码吧) 代码: 1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cmath>

【每日dp】 Gym - 101889E Enigma 数位dp 记忆化搜索

题意:给你一个长度为1000的串以及一个数n 让你将串中的'?'填上数字 使得该串是n的倍数而且最小(没有前导零) 题解:dp,令dp[len][mod]为是否出现过 填到第len位,余数为mod 的状态(dp=0 or 1) 用记忆化搜索来实现,dfs返回1或0. 如果搜到最后一位并且余数为0,返回1. 如果搜到已经更新过的dp状态,直接返回0. 将mod作为全局变量,不断更新mod. 对于每一位 的'? ' 暴力枚举0~9 #include<stdio.h> #include<std

luogu P2657 [SCOI2009]windy数 数位dp 记忆化搜索

题目链接 luogu P2657 [SCOI2009]windy数 题解 我有了一种所有数位dp都能用记忆话搜索水的错觉 代码 #include<cstdio> #include<algorithm> inline int read() { int x = 0,f = 1; char c = getchar(); while(c < '0' || c > '9') c = getchar(); while(c <= '9' && c >= '

hdu3652 数位dp记忆化搜索

从未见过的船新版本数位dp,,省去了预处理过程,直接进行计算 #include<bits/stdc++.h> using namespace std; #define ll long long int dp[20][20][5],n,len,bit[20]; //dp[i][j][k]:到i位数,前面模13=j,前面是1|不是1|有13的状态已经确定,后面的数的个数 //mod:前面%13的余数,flag=0|1|2:pos+1位不是1|是1|有13出现过了,lim:数的限制 int dfs(

hdu 2833 WuKong(最短路径+记忆化搜索)

http://acm.hdu.edu.cn/showproblem.php?pid=2833 大致题意:给定一个无向图,以及悟空和师傅起点与终点,求它们分别从起点到终点的最短路径中经过相同的点的最大个数. 思路:首先dijkstra求出最短路,那么如果有dis[a] + map[a][b] = dis[b],则边(a,b)一定在最短路径上.根据这一定理可以求出所有最短路径.然后类似于求最长公共子序列求经过的相同点的最大个数. 即若a==b ,dp[a][b] = max(dp[i][j]+1)