• 【题解】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也需要记的!!!

  • 相关阅读:
    notepad++ 在所有行末尾增加符号
    Linux
    [论文阅读] MIR音乐信息检索3
    GitLab CE 常规配置与命令 零点
    Java线程学习之Condition条件
    Java线程学习之ReentrantLock锁
    Java线程学习之读写锁
    PyQt(三) 常见控件二
    PyQt5(四) 线程与绘图处理
    Python 开发规范
  • 原文地址:https://www.cnblogs.com/ZhengkunJia/p/14425349.html
Copyright © 2020-2023  润新知