[Solution] [CQOI2016]手机号码

是一道数位DP。可以用记忆化搜索解决。

题目地址



这道题细节非常多。可以考虑分步进行。

  • 求\(L?\)到\(R?\)之间的符合条件的数,可以等价为求\(\text{1~R}?\)之间的符合条件的数减去$text{1~L-1}$之间符合条件的数
  • 那我先写一个搜索求解\(L?\):
void DFS (int X , int K , int U , int _4 , int _8 , int T) {
//变量名介绍:X代表当前的位数,K代表当前位所填的数字,U=1表示卡上界,否则不卡。_4和_8分别是有没有出现过4和8,T=3表示已完成三连号,否则表示未完成,且T等于当前连号。
    if (X == N - 1) {
        if (T == 3) ++ Ans ;
        return ;
    }
//如果符合条件,就累加答案。
    int Goto = Base - 1 ;
    if (U) Goto = L[X + 1] ;
//如果卡了上界,应该进行特判。
    for (int i = 0 ; i <= Goto ; ++ i) {
        if (_4 && i == 8) continue ;
        if (_8 && i == 4) continue ;
        //如果同时出现8和4就跳过这一步。
        if (T == 3) {
            DFS ( X + 1 , i , L[X + 1] == i && U ? 1 : 0 ,
            i == 4 || _4 ,i == 8 || _8 , 3) ;
        } else DFS ( X + 1 , i , L[X + 1] == i && U ? 1 : 0 ,
         i == 4 || _4 ,i == 8 || _8 , i == K ? T + 1 : 1) ;
    }
}
  • 上面的代码可以在数位较少时得出正确结果。但是当数位较大(\(10^{10}≤L<10^{11}?\))就会TLE。
  • 搜索是盲目的,因为它做了很多重复性的工作。(By Instructor Li)

  • 对于\(\text{DFS (int X , int K , int U , int _4 , int _8 , int T)}?\),它的同一组参数所得到的结果应该是一样的。这是进行记忆化搜索的前提。
  • 具体地说,开一个数组\(\text{ F[20][10][2][2][2][4]}\)。其中六个维度分别对应\(\text{DFS}\)的六个参数。
  • 我们每次搜索完成后,把Ans的改变量\(\delta Ans\)结果记录在上述数组里,下一次遇到同一组参数,只要在\(Ans\)增加上已记录的答案,然后返回即可。
  • 改进版本:
void DFS (int X , int K , int U , int _4 , int _8 , int T) {
//这一部分的变量名解释和刚才一样。
    if (X == N - 1) {
        if (T == 3) ++ Ans ;
        return ;
    }
    if (F[X][K][U][_4][_8][T] != -1) {
        Ans += F[X][K][U][_4][_8][T] ;
        return ;
    }
    //遇到同一组参数,只要在$Ans$增加上已记录的答案,然后返回即可。
    F[X][K][U][_4][_8][T] = Ans;
    int Goto = Base - 1 ;
    if (U) Goto = L[X + 1] ;
    for (int i = 0 ; i <= Goto ; ++ i) {
        if (_4 && i == 8) continue ;
        if (_8 && i == 4) continue ;
        if (T == 3) {
            DFS ( X + 1 , i , L[X + 1] == i && U ? 1 : 0 , i == 4 || _4 ,i == 8 || _8 , 3) ;
        } else DFS ( X + 1 , i , L[X + 1] == i && U ? 1 : 0 , i == 4 || _4 ,i == 8 || _8 , i == K ? T + 1 : 1) ;
    }
    F[X][K][U][_4][_8][T] = Ans - F[X][K][U][_4][_8][T] ;
    //把Ans的改变量δAns结果记录在上述数组里
}
  • 这个版本已经可以快速完成求解。

其他要点:

  • 注意开\(\text{long long}\)
  • 注意细节,例如\(L-1\)的边界情况。

Thanks !

原文地址:https://www.cnblogs.com/bj2002/p/11367472.html

时间: 2024-10-10 15:11:59

[Solution] [CQOI2016]手机号码的相关文章

【luogu4124】【bzoj4521】 [CQOI2016]手机号码 [数位dp]

P4124 [CQOI2016]手机号码 4521 这道题要注意卡上下界 我错了 写dfs版的更好考虑状态 写纯方程转移那个细节把我想瓜了 1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<cstring> 5 #include<cmath> 6 #include<stack> 7 #include<algorithm> 8 using

Bzoj4521 [Cqoi2016]手机号码

Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 557  Solved: 335 Description 人们选择手机号码时都希望号码好记.吉利.比如号码中含有几位相邻的相同数字.不含谐音不 吉利的数字等.手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号 码单独出售.为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数 量. 工具需要检测的号码特征有两个:号码中要出现至少3个相邻的相同数字,号码中不能同 时

P4124 [CQOI2016]手机号码

链接:https://www.luogu.org/problemnew/show/P4124 题目描述 人们选择手机号码时都希望号码好记.吉利.比如号码中含有几位相邻的相同数字.不含谐音不吉利的数字等.手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售.为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量. 工具需要检测的号码特征有两个:号码中要出现至少 33 个相邻的相同数字:号码中不能同时出现 88 和 44 .号码必须同时包含两个特征才满

【题解】Luogu P4121 [CQOI2016]手机号码 数位DP

题目传送门 Solution 这里我采用记忆化搜索的形式实现有哪个大佬可以教教我递推板的数位dp啊 搜索的过程中记录是否曾出现8,是否曾出现4,是否曾有连续3个相同数字,搜到底返回就可以了 其实没有太多好写的,数位DP很多都是套模板 那还写来干嘛? 这道题 从$1e10$开始算! 从$1e10$开始算! 从$1e10$开始算! 这就意味着我们需要特殊处理最高位,而不是像普通的数位DP那样直接把最高位也丢进搜索里搜. 惨痛的70分教训大概只有我是这么蠢了嘤嘤嘤 Code #include <cst

【题解】Luogu P4121 [CQOI2016]手机号码

Solution 这里我采用记忆化搜索的形式实现有哪个大佬可以教教我递推板的数位dp啊 搜索的过程中记录是否曾出现8,是否曾出现4,是否曾有连续3个相同数字,搜到底返回就可以了 其实没有太多好写的,数位DP很多都是套模板 那还写来干嘛? 这道题 从\(1e10\)开始算! 从\(1e10\)开始算! 从\(1e10\)开始算! 这就意味着我们需要特殊处理最高位,而不是像普通的数位DP那样直接把最高位也丢进搜索里搜. 惨痛的70分教训大概只有我是这么蠢了嘤嘤嘤 Code #include <cst

【luoguP4124 】[CQOI2016]手机号码

题目描述 人们选择手机号码时都希望号码好记.吉利.比如号码中含有几位相邻的相同数字.不含谐音不吉利的数字等.手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售.为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量. 工具需要检测的号码特征有两个:号码中要出现至少 3 个相邻的相同数字:号码中不能同时出现 8 和 4.号码必须同时包含两个特征才满足条件.满足条件的号码例如:13000988721.23333333333.14444101000.而

bzoj 4521: [Cqoi2016]手机号码

感觉get到了一种数位dp的新姿势,加一位表示当前要填的数有没有限制(感觉以前的写法都太蠢了). 这么写有两个地方要注意: 1.每dp到一位时需要f[i][初始状态]++,相当于这位前都是前导零(这道题我把前两位填了两个10作为初始状态). 2.因为有了1,所以初始状态后的第一位不能填0,需要特判 f[i][j][k][l][p][q][o]表示填到第几位,上上位和上位分别是什么,4,8是否出现过,三个连续的是否出现过,以及当前位是否有限制. #include<iostream> #inclu

4521: [Cqoi2016]手机号码|数位DP

数据范围这么小..感觉暴力可过啊.. DP也是随便设计状态 F[i][j][k][s][l] 表示前i位,最后一位是j 最后一位连续出现k次(如果k已经等于3那么就一直不变)s表示4,8的出现状态 l表示前缀是否和原数的前缀相同 转移就是枚举下一位转移,也很简单.. #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio>

[CQOI2016]手机号码

题面 是不是所有的数位DP都长得一样? (也许是我太菜,只做过最简单的?) 这题也一样,只不过记得东西变了变,直接记搜即可. 代码真的很短,注释在代码里. #include <bits/stdc++.h> using namespace std; typedef long long ll; int num[12],len; ll l,r,dp[11][11][11][2][2][2][2]; ll f(int p,int a,int b,bool c,bool d,bool _4,bool _