草庐IT

数位dp

sjwsjw 2023-04-11 原文

数位dp

思想

一般来说,题目是要求在区间\([l,r]\)中符合某一种条件的数的个数

我们用前缀和的思想考虑,分别求出\([1,r]\)\([1,l-1]\)中数的个数相减即为所求

这里采用记忆化搜索的方式实现

模板

#include<iostream>
#include<cstring>
#include<vector>
#define int long long//这是因为数位问题的结果一般比较大,直接使用longlong
int dp[N][N][……];//DP数组,第一维代表数的长度,其他维由具体问题决定
vector<int>nums;//分解出的每一位数字
int len;
int dfs(int pos, status, int limit, int zero){
    if(pos>len) return //根据status返回结果,不是0就是1;
    if(!zero && !limit && dp[pos][status]!=-1) return dp[pos][status];
    int last = limit ? nums[len-pos-1] : 9;
    int ans = 0;
    for(int i=0;i<=last;++i){
        ans += dfs(pos-1,newStatus,newLead,limit&&i==last);
    }
    if(!zero&&!limit) dp[pos][status] = ans;
        return ans;
}
int calc(int n){
    if(!n) return //视具体情况
    nums.clear();
    memset(dp,-1,sizeof dp);
    while(n) nums.push_back(n%10),n/=10;
    len = nums.size();
    return dfs(0,……)
}

说明

\(zero\)表示是否有前导零,当考虑的数是否需要记录和数位中0有关的数时需要前导零标记,无关时可以没有\(zero\)标记

举个例子:假如我们要从 \([0,1000]\) 找任意相邻两数相等的数

显然 \(111,222,888\) 等等是符合题意的数

但是我们发现右端点 \(1000\) 是四位数

因此我们搜索的起点是 \(0000\) ,而三位数的记录都是 \(0111,0222,0888\) 等等

而这种情况下如果我们直接找相邻位相等则 \(0000\) 符合题意而 \(0111,0222,0888\) 都不符合题意了

所以我们要加一个前导0标记

例题1 Windy数

#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
int dp[12][12],len;
vector<int>nums;
int dfs(int pos,int pre, int limit,int zero){
	if(pos>len-1) return 1;
	if(!limit&&!zero&&dp[pos][pre]!=-1) return dp[pos][pre];
	int ans = 0;
	int res = limit?nums[len-pos-1]:9;
	for(int i=0;i<=res;i++){
		if(i<pre+2&&i>pre-2&&!zero) continue;
		ans += dfs(pos+1,i,limit&&(i==res),!i&&zero);
	}
	if(!limit&&!zero) dp[pos][pre] = ans;
	return ans;
}
int calc(int n){
	if(n==0) return 1;
	memset(dp,-1,sizeof dp);
	nums.clear();
	while(n) nums.push_back(n%10),n/=10;
	len = nums.size();
	return dfs(0,-2,1,1);
}
int main(){
	int a,b;
	cin>>a>>b;
	cout<<calc(b)-calc(a-1);
	return 0; 
} 

例题2 手机号码

#include<iostream>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
int f[15][15][15][2][2][2],len; 
//[位置][前一位][前两位][是否有8][是否有4][是否连续3个] 
vector<int>nums;
int dfs(int pos,bool limit,int pre1,int pre2,bool _8,bool _4,bool _3){

	if(_8&&_4) return 0;
	if(pos>len-1) return _3;
	if(!limit&&f[pos][pre1][pre2][_8][_4][_3]!=-1) return f[pos][pre1][pre2][_8][_4][_3];
	int ans = 0;
	int res = limit?nums[len-pos-1]:9;
	for(int i=0;i<=res;i++){
		ans+=dfs(pos+1,limit&&(i==res),i,pre1,_8||i==8,_4||i==4,_3||(i==pre1&&pre1==pre2));
	}
	if(!limit) f[pos][pre1][pre2][_8][_4][_3] = ans;
	return ans;
}
int calc(int n){
	memset(f,-1,sizeof f);
	nums.clear();
	while(n){
		nums.push_back(n%10);
		n/=10;
	}
	if(nums.size()!=11) return 0;
	int res = 0;
	len = nums.size();
	for(int i=1;i<=nums[len-1];i++){
		res+=dfs(1,i==nums[len-1],i,0,i==8,i==4,0);
	}

	return res;
}
signed main(){
	int l,r;
	cin>>l>>r;
	cout<<calc(r)-calc(l-1)<<endl;
	return 0;
} 

例题3 圆数

#include<iostream>
#include<cstring>
#include<vector>
#include<cstring>
using namespace std;
int f[35][35][35],len;
vector<int>nums;
int dfs(int pos,int sum1,int sum0,int limit,int zero){
	if(pos>len-1) return sum0>=sum1;
	if(!limit&&!zero&&f[pos][sum1][sum0]!=-1) return f[pos][sum1][sum0];
	int ans = 0;
	int res = limit?nums[len-pos-1]:1;
	for(int i=0;i<=res;i++){
		if(zero&&(!i)) ans+=dfs(pos+1,0,0,limit&&i==res,1);
		else{
			ans+=dfs(pos+1,sum1+(i==1),sum0+(i==0),limit&&i==res,0); 
		}
	}
	if(!limit&&!zero) f[pos][sum1][sum0] = ans;
	return ans;
}
int calc(int n){
	if(!n) return 1;
	nums.clear();
	memset(f,-1,sizeof f);
	while(n) nums.push_back(n%2),n/=2;
	len = nums.size();
	int res = 0;
	res += dfs(0,0,0,1,1);
	return res;
}
int main(){
	int l,r;
	cin>>l>>r;
	cout<<calc(r)-calc(l-1);
	return 0;
}

例题4 同类分布

#include<iostream>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
int f[20][200][200],len,mod;
vector<int>nums;
int dfs(int pos,int sum,int st,int limit){
	if(pos>len-1) return sum==mod&&st==0;
	if(!limit&&f[pos][sum][st]!=-1) return f[pos][sum][st];
	int ans = 0;
	int res = limit?nums[len-pos-1]:9;
	for(int i=0;i<=res;i++){
		ans+=dfs(pos+1,sum+i,(10*st+i)%mod,limit&&(i==res));
	}
	if(!limit) f[pos][sum][st] = ans;
	return ans;
}
int calc(int n){
	if(!n) return 0;
	nums.clear();
	int res = 0;
	while(n) nums.push_back(n%10),n/=10;
	len = nums.size();
	for(mod=1;mod<=9*len;mod++){
		memset(f,-1,sizeof f);
		res+=dfs(0,0,0,1);
	}
	return res;
}
signed main(){
	int a,b;
	cin>>a>>b;
	cout<<calc(b)-calc(a-1);
	return 0;
}

有关数位dp的更多相关文章

  1. ruby-on-rails - 如何计算Float中的小数位数? - 2

    我正在使用Ruby1.8.7和Rails2.3.5。如果我有一个像12.525这样的float,如何获取小数点后的位数?在这种情况下,我希望得到“3”。 最佳答案 我猜是这样的:n=12.525n.to_s.split('.').last.size 关于ruby-on-rails-如何计算Float中的小数位数?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8597766/

  2. ruby - 如何将 ruby​​ BigDecimal 转换为 2 位小数位字符串? - 2

    我想将RubyBigDecimal对象转换为漂亮的、可打印的货币值。所以我想让它打印到小数点后两位。我该怎么做?如您所见,以下方法均无效:irb(main):033:0>v=BigDecimal("7.1762")=>#irb(main):034:0>v.to_s('2F')=>"7.1762"irb(main):035:0>v.to_s('F')=>"7.1762"irb(main):036:0>v.to_s('%0.2F')=>"0.71762E1"irb(main):037:0>v.to_s('%0.2f')=>"0.71762E1"irb(main):038:0>哪个表达式只会

  3. ruby-on-rails - to_d 在 ruby​​ 中总是返回 2 个小数位 - 2

    我正在处理货币,我想将数字向下舍入到小数点后两位。即使数字是500.0,我也希望它是500.00以保持一致。当我执行“500.00”.to_d时,它会将其转换为500.0。改变这种行为的好方法是什么?我还使用这种方法向下舍入到2位数字,并确保它始终有2位小数。defself.round_down(x,n=2)s=x.to_sl=s.index('.')?s.index('.')+1+n:s.lengths=s[0,l]s=s.index('.')?s.length-(s.index('.')+1)==1?s 最佳答案 除了mcfin

  4. javascript - 按小数位格式化数字 - 2

    我正在尝试转换一组数字,使每个数字只有一个非零数字。所以基本上"7970521.5544"会给我["7000000","900000","70000","500","20","1",".5",".05",".004",".0004"]我试过:varj="7970521.5544"vark=j.replace('.','')varresult=k.split('')for(vari=0;i任何想法,我不确定从这里去哪里? 最佳答案 算法:Split使用十进制表示法将数字分为两部分。运行一个for循环,将每个数字乘以相应的10次幂,例如

  5. javascript - 在 Javascript 中生成一个介于 2 个值和 2 个小数位之间的随机数 - 2

    我想生成一个1到10之间的随机数,最多保留2位小数,我目前正在使用下面的这个来生成我的号码,varrandomnum=Math.floor(Math.random()*(10.00-1.00+1.00))+1.00;最后,我想知道如何生成这样的数字:1.665.868.34格式为:varrandomnum=然后是代码旁注:我不记得为什么我以前那样生成我的数字,但记得一些关于Math.random生成小数点后8位数字的事情。感谢您的帮助!:)Ps:我看过很多关于等待向下或向上舍入生成的数字的帖子,但还没有找到一个想要直接生成它们的帖子。更新:我想要一个数字值而不是一个看起来像数字的字符串

  6. javascript:缩短大数,强制保留小数位,并选择将 1000 表示为 .001 百万 - 2

    我正在努力完成三件事-我想缩短大数字并添加K/M/B后缀我希望能够强制小数位数我希望能够强制将数千表示为百万的小数只需缩短,四舍五入到小数点后两位1200000---->>>120万1248000---->>>125万248000---->>>248K缩短,强制保留2位小数1200000---->>>120万1248000---->>>125万248000---->>>248.00K缩短,强制小数点后3位,强制几千到几百万1200000---->>>1.200M1248000---->>>1.248M248000---->>>0.248M我有一个javascript函数,我发现它可以做

  7. 正则表达式的 JavaScript 小数位限制 - 2

    我有一些针对文本框的客户端验证,它只允许最多两位小数的数字,没有其他输入。此脚本仅作为输入数值的基础,但需要对其进行调整,以便它可以采用小数点后跟最多两位小数。我已经尝试过诸如/[^\d].\d{0,2}之类的东西,但是替换调用不起作用,我不知道如何去做。代码functionvalid(f){if(!/^\d*$/.test(f.value)){f.value=f.value.replace(/[^\d]/g,"");alert("Invalidnumber");}}注意事项我需要匹配一个空字符串。如果提供空字符串并提交表单,则该值默认返回零。 最佳答案

  8. xml - 具有基于变量的小数位数的 XPath 格式数字? - 2

    我有一个XML文档,其中应报告特定xs:decimal的小数位数保存在同级节点中。我目前正在努力寻找一种简单的方法来通过format-number函数输出它。我可以使用其他一些函数构建一个图片字符串,但是对于本应(至少在我看来)相对简单和常见的任务来说,这似乎过于冗长了。例如我目前正在做的是这样的:有没有更好的办法? 最佳答案 很好的问题!这通常意味着,我不知道答案,但我希望其他人知道,因为这对我来说也是一种痛苦。无论如何,我做了一些搜索,我认为round-half-to-even函数可能会成功(http://www.xqueryf

  9. xml - dp :serialize and escaping on ibm datapower - 2

    我有一个项目,我需要对一个xml文件进行二进制64位编码并将其放入另一个xml中。为了让它工作,我首先使用dp:serialize序列化xml,然后对由此产生的变量使用dp:binary-encode。除了所有斯堪的纳维亚字符都被转义之外,这工作正常。当我解码结果时,åäö变成了åäö。有什么想法吗?我试过在输出标签上使用dp:escaping="minimum"(xsl:output标签会影响dp:serialize吗?)和许多其他选项。通过在二进制64位编码之前打印序列化结果,我看到在调用dp:serialize时添加了转义。是否可以在不转义数据电源的情况下进行序列化?

  10. xml - 需要 XML 模式的小数位数 - 2

    我想将数字的小数位数限制为正好2。例如-1.00有效,但1、1.0和1.000均无效。这是一个类似的问题:Specifyamountofdecimalplacesofanxs:decimalinanXMLschema这是提供的答案:不幸的是,这仅将小数位数限制为2,并允许具有零位或一位小数的数字。 最佳答案 您可以使用正则表达式将小数位数强制为正好两位,但这样做明智吗?对于生成本应符合此模式的文档的任何人来说,您的生活都变得更加艰难。例如,他们将无法使用XSLT或XQuery生成的默认序列化,而必须自己编写。将规范更改为在接受的内容

随机推荐