首页 > 其他分享 >[笔记]数位dp例题及详解(更新中)

[笔记]数位dp例题及详解(更新中)

时间:2024-04-06 22:25:22浏览次数:22  
标签:int sum pos limit 例题 dp 数位

数位dp的定义引自洛谷日报#84

求出在给定区间\([L,R]\)内,符合条件\(f(i)\)的数\(i\)的个数。条件\(f(i)\)一般与数的大小无关,而与数的组成有关。
由于是按位dp,数的大小对复杂度的影响很小

由于数位dp状态的上下文信息比较多,所以一般用记忆化搜索实现,而非递推。

P4999 烦人的数学作业

题意简述:\(T\)次询问,每次给定\(L,R\),输出\([L,R]\)中所有数的数位和之和。
\(1\leq L\leq R\leq 10^{18},1\leq T\leq 20\)

我们发现范围很大,如果模拟会超时。所以引入数位dp的做法,数位dp一般会利用前缀和的思想,把\([L,R]\)转化为\([1,R]-[1,L-1]\),那么怎么计算\([1,x]\)呢?

用\(f[i][j]\)表示从最高位开始填了\(i\)位,数位和为\(j\)的答案。

思考如何转移:因为我们从最高位开始填,那么显然每一位都有限制。拿\(520\)举例:

  • 第\(1\)位如果填\(0\sim 4\),那么后面可以随便填没有限制。
  • 第\(1\)位如果填\(5\),那么第\(2\)位就要受限,如果填\(0\sim 1\)就和第一条一样,填\(2\)就是第二条,这样循环下去直到填完……

所以\(dfs\)的参数应有三个。

  • \(pos\),表示当前正在填哪一位,从最高位\(len\)(原数的位数)开始往前填,根节点\(pos=len\),即正在填最高位。\(pos=0\)为结束条件。
  • \(limit\),bool类型,表示当前这一位有没有限制。
  • \(sum\),表示从最高位填到\(pos+1\)数位和是多少,用作递归结束的返回值。

但是我们发现这样就是一个普通的模拟,把所有数都试了一遍。所以需要记忆化,如果\(f[pos][sum]\)已经计算过了,直接返回即可(需要注意\(limit=false\)时才能用)。

为什么\(limit=false\)才需要记忆化,是因为\(limit=true\)的情况只有\(i=a[pos]\)(见代码),所以在递归树上只是一条链,没有记忆的必要。

至于时间复杂度,那就是状态数量,换句话说就是\(f\)数组的大小,因为调用是\(f[pos][sum]\),所以大小是\(len*10len\)即\(log_{10}r\),总\(O(T\ log_{10}r)\)

思考:传递的参数中如果有数组等,为了节省空间,把它定义成全局数组,然后利用回溯传递状态更好(这里的\(pos,sum\)就可以这样子优化掉,但是本身递归层数就不多,所以影响几乎没有)。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
int f[25][250],a[25],t,l,r;
int dfs(int pos,bool limit,int sum){
	if(pos==0) return sum;
	if(!limit&&f[pos][sum]) return f[pos][sum];
	int rig=limit?a[pos]:9;
	int ans=0;
	for(int i=0;i<=rig;i++){
		ans=(ans+dfs(pos-1,limit&&i==rig,sum+i))%mod;
		//依次枚举这一位填什么
		//如果这一位没有限制,那么填前一位也一定没有限制。
		//如果这一位有限制,那么只有这一位填的数为a[pos]时才有限制(具体上面有说明)
	}
	if(!limit) f[pos][sum]=ans;
	return ans;
}
int solve(int x){//把x的值存入a数组
	int len=0;
	while(x){
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,1,0);
}
signed main(){
	cin>>t;
	while(t--){
		cin>>l>>r;
		cout<<(solve(r)-solve(l-1)+mod)%mod<<endl;
	}
	return 0;
}

P2602 [ZJOI2010] 数字计数

与刚才那道很像哦,只不过询问的是\(0\sim 9\)每个数字出现多少次。一个求和,一个计数。

似乎数位dp一般思路就是先写一个暴搜,然后再思考怎么记忆化。一开始想到两种暴搜思路:

  • 将状态表示成\(sta[10]\),为了省空间不再通过参数传递,而是开一个全局,通过回溯实现。传递的参数有\(pos\)和\(limit\),含义和上面的一样。具体过程也差不多,就是把所有状态枚举一遍。\(pos=0\)时结束,把当前的\(sta\)加入到\(ans\)数组中。最后输出\(ans\)。
  • 不用数组表示状态,开三个参数\(pos,limit,cnt\),其中\(cnt\)表示数字\(x\)的出现次数(与上面的\(sum\)类似,不过一个是求和,一个是计数),\(pos=0\)结束,返回\(cnt\)。主函数中调用\(10\)次\(dfs\),\(x\)分别取\(0\sim 9\)。调用一次输出一个。

(注:这两种思路都需要处理前导\(0\)的情况,所以上面只是简单的思路,\(dfs\)的形参可能会随需求有增加)

[留坑]

标签:int,sum,pos,limit,例题,dp,数位
From: https://www.cnblogs.com/Sinktank/p/18117420

相关文章

  • socket编程——C++实现基于UDP协议的简单通信(含详解)
    文章后面有代码,可以直接复制在VisualStudio2022中运行(注意:必须是两个项目,客户端服务端各一个,连接在同一网络中,先运行服务端,并且客户端数据发送的目标IP要改为你服务端的IP)目录前言帮助文档一、UDP通信框架1.服务端2.客户端二、服务端实现1.加载库(WSAStartup函数)......
  • 背包DP
    01背包定义dp[i][j]表示从前i件物品中选,体积不超过j的最大价值N,V=map(int,input().split())v=[0]*(N+1)w=[0]*(N+1)foriinrange(1,N+1):v[i],w[i]=map(int,input().split())f=[[0]*(V+1)for_inrange(N+1)]#对于第i件物品,选或......
  • Windows 11 RDP 设置自定义证书
    1.随便生成一个证书或者去freessl之类的地方申请一个证书2.将证书转换成pfx格式opensslpkcs12-export-inkeyprivate_key.key-incertificate.pem-certfileCACert.pem-outcertificate.pfx3.打开certlm右键个人->所有任务->导入,导入刚刚创建的pfx证书......
  • 数位DP
    CF204A题目链接https://codeforces.com/problemset/problem/204/A题目大意模板讲解数位DP模板#include<bits/stdc++.h>#definelllonglongusingnamespacestd;intn,m,t;lll,r;strings;llmemo[20][10][10];lldfs(inti,intfirst,intlast,boolis_limi......
  • Offer必备算法21_回文串dp_六道力扣题详解(由易到难)
    目录①力扣647.回文子串解析代码②力扣5.最长回文子串解析代码③力扣1745.分割回文串IV解析代码④力扣132.分割回文串II解析代码⑤力扣516.最长回文子序列解析代码⑥力扣1312.让字符串成为回文串的最少插入次数解析代码本篇完。①力扣647.回文子串64......
  • flask 装饰器 AssertionError: View function mapping is overwriting an existing en
    1问题描述写了一个登陆认证装饰器,部分试图,只有用户登陆才能访问deflogin_wrapper(func):definner(*args,**kwargs):"""判断是否登陆若是进入视图函数否则重定向到登陆页面"""if......
  • C语言经典例题(17) --- 最小公倍数、单词倒置、你是天才吗?、完美成绩、判断整数的奇偶
    1.最小公倍数正整数A和正整数B的最小公倍数是指能被A和B整除的最小的正整数,设计一个算法,求输入A和B的最小公倍数。输入描述:输入两个正整数A和B。输出描述:输出A和B的最小公倍数。输入:57输出:35#include<stdio.h>intmain(){inta=0;intb=0;int......
  • C语言经典例题(18) --- 判断字母、三角形判断、衡量人体胖瘦程度、翻转金字塔图案、平
    1.判断是不是字母题目描述:KK想判断输入的字符是不是字母,请帮他编程实现。输入描述:多组输入,每一行输入一个字符。输出描述:针对每组输入,输出单独占一行,判断输入字符是否为字母,输出内容详见输出样例。输入:A6输出:Aisanalphabet.6isnotanalphabet......
  • 用UDP协议实现发送接收的网络聊天室
     发送数据 UDP协议是面向无连接的"面向无连接的"通常指的是一种网络通信模式,也称为无连接通信或者数据报通信。在这种模式下,通信的两个端点之间不需要建立持续的连接,而是通过将数据分成小块(数据包)并单独发送来进行通信。每个数据包都包含了足够的信息(如源地址、目标地址......
  • 状压DP
    CF580D题目链接https://codeforces.com/problemset/problem/580/D题目大意思路令dp[i][j]表示,吃菜状态为i,且最后一道菜为j的最大满足感!代码#include<bits/stdc++.h>#definelllonglongusingnamespacestd;constintN=20;intn,m,t,q;inta[N],b[N*N][N*......