【题解】HUD3652 B-number && 数位DP学习笔记

数位DP

一般求区间内满足限制的数的个数,状态设计考虑几位数和大小关系。

条件一般与数的大小无关,而与数的组成有关。

善于用不同的进制处理。

数位DP外套网络流、贪心、枚举。

一般的数据范围是1 e 9到1 e 18。

填数的过程中要满足上限限制和题目其他限制。

一般用记忆化搜索求答案。

HDU3652

统计区间 [1,n] 中含有 '13' 且模 13 为 0 的数字有多少个。

N<=10^9

分析

设f[i][j][0/1][0/1]表示位数为i,数位之和为j,是否含有数字13,最后一位是否是1的数字的个数。

f不直接求,而是记忆化搜索,一边试填一边求f。

输入数n,设n的位数是m,我们想构造一个小于n且满足条件的数字。问题转化为诸位填数。

假如n = 13245,现在前3位填的是132,那么为了得到的数小于n,第4位不能大于4。

如果现在前3位填的是非132的任意合法的数(不能是999这样的),那么第4位可以填0~9中任意一个。

如果前面填的数中出现了13,后面无论填什么都是满足这个条件的。

如果前面填的数中没有出现13,但是上一位数字是1,那么当前位置填3的话,就有13了。

如果前面填的数的数位之和模13是sum,我们枚举当前位置所填的数字i,当前数字的数位之和模13就是(sum + i * ten[pos]) mod 10,其中pos是从低到高第pos位,ten[pos]表示10的幂次,需要预处理。

代码


    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<string>
    using namespace std;
    
    long long read()
    {
    	long long x=0,f=1;
    	char ch=getchar();
    	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int n, m, num[20];
    int ten[20], f[20][13/*数字和*/][2/*前面是否出现过13*/][2/*最后一位是否是1*/];//[2/*是否达到上限*/];
    int dfs(int pos, int sum, bool hav13, bool limit, int last){
    	if(pos == 0 && sum == 0 && hav13){
    		return 1;
    	}
    	if(pos == 0){
    		return 0;
    	}	
    	if(!limit && f[pos][sum][hav13][last == 1] != -1){
    		return f[pos][sum][hav13][last == 1];	
    	} 
    	int res = 0, up;
    	if(limit == 1) up = num[pos];
    	else up = 9;
    	for(int i = 0; i <= up; ++i){
    		res += dfs(pos - 1, (sum + i * ten[pos]) % 13, hav13 || (last == 1 && i == 3), limit && (i == up/*i == num[pos]*/), i);
    	}
    	if(!limit) f[pos][sum][hav13][last == 1] = res;
    	return res;
    }
    int main()
    {
    	memset(f, -1, sizeof(f));
    	ten[1] = 1;
    	for(int i = 2; i <= 13; ++i)	ten[i] = ten[i - 1] * 10;
    	while(scanf("%d", &n) != EOF ){
    		m = 0;
    		while(n){
    			num[++m] = n % 10;
    			n /= 10;
    		}
    		printf("%d
", dfs(m , 0, 0, 1, 0));
    	}
    	return 0;
    }

关于数位dp的经验

  • 注意很多时候带进去是n==0要特殊处理。 ◦

  • 还有一般问[m,n],我们求[1,n]-[1,m-1]但是有的时候m为0就炸了。然 后一道题wa一个小时。。。。。。正常。。。。。

  • 求所有包含49的数,其实就是(总数-所有不包含49的数)。前者的化需要有两维限制,一个是上一位是什么,一个是之前有没有49。但是后者只需要记一个上一位是什么。就能好写一些。

  • 一般问题的数位dp部分,都是套路,但是这并不代表它外面“华丽的 外衣”和与其他算法结合的的部分也是无脑的。要看出它是考数位dp, 要看出问题怎么变化一下就是数位dp了。

  • dp初始化memset要置为-1。不能置为0!!!!!!因为有很多时候 dp值就应该是0,然后我们如果误以为是因为之前没有计算,从新计算的话,就会tle。这里不能写成0。

  • 既然是记忆化搜索,那就可以剪枝!!!!可行性剪枝!!

  • 有时前导0也需要记的!!!

原文地址:https://www.cnblogs.com/ZhengkunJia/p/14425349.html