首页 > 其他分享 >数位DP详解

数位DP详解

时间:2022-08-29 15:44:18浏览次数:99  
标签:return int pos 详解 limit include DP 数位

数位dp一般针对统计某个区间符合一个或多个条件的数的数量,因为算法名字叫数位dp,所以我们需要对数位进行枚举

思路

考虑,传统做法,例如统计[L, R]之间有多少个数,该数至少含有一个1,传统做法,直接暴力,算出[0, R], [0, L - 1], 类似于前缀和一样,然后相减即可。但是复杂度无疑来到了o(N²), 还带有一个很大的常数。R一旦到1e6左右就寄了。
而数位dp提供了一种新思路,利用记忆化搜索(dp的一种实现方式),枚举数位的方式,减少了很多复杂状态的重复计算。

举个例子:
例如现在是算[0, 114519]
已经枚举完了11,现在是11XXXX,这个时候第一个X等于1,2,3的时候,种类数相同,因为后面的3个X可以任意枚举0-9(等于4的时候就不行,后面的第二个x就有5的上限的限制),这就是重复状态,数位dp,可以将这重复状态的计算给去掉。

还有一个特殊的限制是前导0签到爆0,例如4231,我们在记忆化dfs的时候,会出现00XX的情况,因为需要枚举0,0可能在中间(属于合法状态),但是如果前面全是0的这种情况(有些题目是合法的,有些题目不合法,根据题目而来)。所以我们需要维护以下几种东西:

1、pos 当前位置pos
2、limit 当前数位pos是否到达枚举上限
3、lead 前导0是否存在
4、others 其他题目给出的限制条件

模板

该算法模板大部分基本一致,dfs功底扎实的一下就能看懂!

int f[pos][limit][lead]
int dfs(int pos, ..., bool limit, bool lead)
{
    if (pos == cnt)//枚举到最后一位
        return 1;//不一定是1 有时候是题目限制
    int ans = 0;
    if (f != -1) return f;//该状态已经计算过了
    for (int v = 0; v <= (limit ? A[pos] : 9); v++)//根据limit判断枚举上限
    {
        //limit 和 lead 与当前位置的限制关系都是 &&
        ans += dfs(pos + 1, limit && v == A[pos], lead && v == 0);
    }
    return f = ans;
}

int fx(int x)
{
    if (x == 0)return 0;
    cnt = 0;
    memset(A, 0, sizeof(A));
    memset(dp, -1, sizeof(dp)); //初始化为-1 判断是否存在该状态 某些状态0也算 所以不能赋值为0
    while(x) A[cnt++] = x % 10, x /= 10;
    reverse(A, A + cnt);//反转一下 还正着从左往右数 理解前导0
    return dfs(0, true, true);//第一位有限制 true 前缀都看为0 为true
}

例题

不要62

传送门
Des
数位不能存在62连起来,也不能含有4。
Solution
注意:这里它是个车牌号,前导0是合法的,所以我们不用判断前导0,多维护一个信息,上一位是几,如果last = 6, pos = 2,就跳过不用枚举,pos = 4的情况也跳过就行。
Code

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int cnt, A[10];
int f[12][20][2];//pos last limit
int dfs(int pos, int last, int limit)
{
    if(pos == cnt) return 1;
    if(f[pos][last][limit] != -1) return f[pos][last][limit];
    int ans = 0;
    for(int v = 0; v <= (limit ? A[pos] : 9); ++v)
    {
        if((last == 6 && v == 2) || v == 4) continue;
        ans += dfs(pos + 1, v, limit && A[pos] == v);
    }
    return f[pos][last][limit] = ans;
}
int fx(int x)
{
    cnt = 0;
    memset(A, 0, sizeof A);
    memset(f, -1, sizeof f);
    while(x) A[cnt++] = x % 10,  x /= 10;
    reverse(A, A + cnt);
    return dfs(0, 11, true);//11表示上一位不存在
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    int L, R;
    while(cin >> L >> R, L || R)
    {
        cout << fx(R) - fx(L - 1) << "\n";
    }
    return 0;
}

数位小孩

传送门
Des
限制:
相邻数位和是否是素数,
至少含有一个1,
没有前导0,
Soluiton
素数和好说,类似于上一题,记录一个last(上一位),判断last + pos是否为素数就行,
至少含有一个1,这个我们不能因为当前pos不是1,而直接return,因为后面的位可能可以枚举1,所有用flag变量,记录当前的数位是否有1,直到最后枚举完所有数位,返回flag(这也是为什么,return 那里不一定是1,也有可能是限制条件)
Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#define int long long//记得LL
using namespace std;
int cnt, A[20];
int f[20][20][2][2][2];
int vis[40];
int dfs(int pos, int last, bool limit, bool lead, bool flag)
{
    if(pos == cnt) return flag;
    if(f[pos][last][limit][lead][flag] != -1) return f[pos][last][limit][lead][flag];
    int ans = 0;
    for(int v = 0; v <= (limit ? A[pos] : 9); ++v)
    {
        if(lead)
            ans += dfs(pos + 1, v, limit && v == A[pos], lead && v == 0, flag || v == 1);
        else if(vis[v + last])
            ans += dfs(pos + 1, v, limit && v == A[pos], lead && v == 0, flag || v == 1);
    }
    return f[pos][last][limit][lead][flag] = ans;
}
int fx(int x)
{
    cnt = 0;
    memset(A, 0, sizeof A);
    memset(f, -1, sizeof f);
    while(x) A[cnt++] = x % 10, x /= 10;
    reverse(A, A + cnt);
    return dfs(0, 11, true, true, false);
}
signed main()
{
    vis[2] =  1;
    vis[3] =  1;
    vis[5] =  1;
    vis[7] =  1;
    vis[11] =  1;
    vis[13] =  1;
    vis[17] =  1;
    int L, R;
    cin >> L >> R;
    cout << fx(R) - fx(L - 1) << "\n";
    return 0;
}

数字计数

传送门
Des
出现0 - 9 的每个数位的次数
Solution
一下子统计出来,不太好搞,一次找一个数位即可。
Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define int long long
using namespace std;
int cnt, A[20];
int dight;
int f[20][20][2][2];//pos cntd lead limit
int dfs(int pos, int cntd, bool lead, bool limit)
{
    if(pos == cnt) return cntd;
    if(f[pos][cntd][lead][limit] != -1) return f[pos][cntd][lead][limit];
    int ans = 0;
    for(int v = 0; v <= (limit ? A[pos] : 9); ++v)
    {
        //不能合起来,计算0的时候 v == dight 0会多算
        if(lead && v == 0)
            ans += dfs(pos + 1, cntd, true, limit && v == A[pos]);
        else
            ans += dfs(pos + 1, cntd + (v == dight), false, limit && v == A[pos]);
    }
    return f[pos][cntd][lead][limit] = ans;
}
int fx(int x)
{
    cnt = 0;
    memset(A, 0, sizeof A);
    memset(f, -1, sizeof f);
    while(x) A[cnt++] = x % 10, x /= 10;
    reverse(A, A + cnt);
    return dfs(0, 0, true, true);
}
signed main()
{
    int L, R;
    cin >> L >> R;
    for(int i = 0; i <= 9; ++i)
    {
        dight = i;
        cout << fx(R) - fx(L - 1) << " ";
    }
    return 0;
}

完结撒花。
参考一下两边博客,写了一些自己理解的其他东西。
[博客1](https://zhuanlan.zhihu.com/p/348851463)
[博客2](https://zhuanlan.zhihu.com/p/465885658)

标签:return,int,pos,详解,limit,include,DP,数位
From: https://www.cnblogs.com/-ytz/p/16636155.html

相关文章

  • 【ubuntu】解决Could not get lock /var/lib/dpkg/lock-frontend
    1、问题在执行一键安装k8s,ssh到其他节点更新apt源,我手动执行update的时候,报的这个错  2、处理方法psaux|grepapt找出对应的进程id,然后killsudokill3374......
  • 详解 OpenDAL |Data Infra 研究社第三期
    你是否对 OpenDAL的设计和使用还有不解,急需一个系统的解释去深入了解呢?对于 OpenDAL在Databend中的应用是否了解?本次直播我们会携手旋涡老师一起为大家答疑解惑,学习......
  • maybe_serialize() | WordPress序列化数据/数组/对象
    函数maybe_serialize(string|array|object$data)描述该WordPress函数可将数组/对象/字符串序列化。参数$data,(string|array|object)需要序列化的数据。返回值(m......
  • 【三维地图】开发攻略 —— 详解“GeoJSON”技术和应用场景
    GeoJSON,一个用于存储地理信息的数据格式。GoeJSON对象可以表示几何、特征或特征集合,支持:点、线、面、多点、多线、多面和几何集合。在基于平面地图,三维地图中都需要用到的......
  • Logstash配置详解
    转自:https://blog.csdn.net/hushukang/article/details/844231841.InputPlugin1.1从文件输入从文件读取数据,如常见的日志文件。文件读取通常要解决几个问题:序号......
  • “轻松搞定CMake”系列之find_package用法详解
    本文是“轻松搞定CMake”系列博客中的一篇,该篇文章的主要目的是详细讲解一下CMake中搜包命令find_package的使用和原理。其他更多文章请参考:“轻松搞定CMake”系列博客......
  • cmake find_package路径详解
    Motivation经常在Linux下面写C++程序,尤其是需要集成各种第三方库的工程,肯定对find_package指令不陌生。这是条很强大的指令。可以直接帮我们解决整个工程的依赖问题,自动......
  • vue项目mixin.js的使用及注意详解
     简单的介绍下mixin(混入):官方介绍:混入(mixin)提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有......
  • QT UDP通信聊天程序(单播、广播、组播)
    QTUDP通信(单播、广播、组播)  日期:2021-03-26    浏览:126    评论:0    核心提示:1.QUdpSocketUDP是轻量的、不可靠的、面向数据报、无连接的协议,它可以用......
  • Arrange the Bulls(状压dp)
    ArrangetheBulls(状压dp)题目大意:一些牛喜欢一些地方(每头牛都有一些喜欢的地方),现在要把这些地方分配给牛,每头牛都应该分到一个地方,问有多少种分配的方法此题拥有着状压d......