• 数位dp杂记


    突然想对数位dp做个总结,奈何有些做过的题目忘记题号了,待发现的时候会加上,所以本文会随时更新

    数位dp,顾名思义,就是对数字的每一位进行dp,但是它的本质就是记忆化搜索

    所以首先我们要把数字拆开:

    fx(int,solve)(int num){
    	set(dig,0,dig,1);high=0;
    	while(num){
    		dig[++high]=num%10;
    		num/=10;
    	}
    	return dp(...);
    }
    

    这种dp通常与数字的大小无关,而与状态数和数字位数有关


    例如我们要找一个区间 ([l,r]) 中不含数字 (4) 的数的个数

    很容易想到枚举每一位,之后对每一位进行讨论

    但是状态数是 (9^n) 的,需要优化

    我们很容易想到:假设现在考虑到了第 (np) 位,并且前 (np-1) 位都没有 (4)

    那么这一位不管选多少(除 (4) 外),都是剩下 (n-np) 位,并且它们的dp结果相同

    我们可以用一个数组 ( ext{dpv}[np]) 来记录剩下 (n-np) 位的方法数

    ( ext{dpv}) 数组经常用来记录某个状态下得到的结果,以防止搜索重复状态,不同情况下可能要多开几维

    在对上界或是前导 (0) 进行处理的时候,我们可以多传几个参数来记录当前情况,以方便用来判断

    具体情况具体分析


    [HDU2089] 不要62

    题意简介:

    求出给定区间中满足:(6)(2) 不能相邻且不含 (4) 的数的个数

    分析:

    超级简单的入门题

    对于不含 (4) 我们可以在枚举的时候判断

    (6)(2) 不能相邻的情况我们可以用变量 (pre) 记录上一位选的数,之后如果 pre==6&&i==2 就跳过,统计即可

    #define fx(l,n) inline l n
    fx(int,dp)(int np,int pre,bool limit){
        if(!np) return 1;
        if(!limit&&dpv[np][pre==6]) return dpv[np][pre==6];
        int al=limit?digit[np]:9,sum=0;
        for(int i=0;i<=al;i++){
            if(i==4||(pre==6&&i==2)) continue;
            sum+=dp(np-1,i,limit&&i==al);
        }
        if(!limit) dpv[np][pre==6]=sum;
        return sum;
    }
    

    [ZJOI2010] 数字计数

    题意简介:

    求出在给定区间的所有正整数中,每个数码出现的次数

    分析:

    对于一个整数 (overline{ABCDcdots Z}) 我们可以将其分开统计,即先统计 (overline{A000cdots0}) 再统计 (overline{B00cdots0}) 以此类推,直到最后

    对于 (n) 位数 (overline{A00cdots0}) 不难发现就是将区间 ([000cdots0,999cdots9]) 每个数码出现的次数统计起来,乘 (A),最后加上 (A) 出现的次数即可

    之后我们将统计的前导 (0) 减掉,顺次统计

    #define fx(l,n) inline l n
    #define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
    fx(void,solve)(int num){
    	int cp=num;
    	set(digit,0,int,50);high=0;
    	set(ans,0,int,N);
    	while(num){
    		digit[++high]=num%10;
    		num/=10;
    	}
    	for(R i=high;i>0;i--){
    		for(int o=0;o<=9;o++) ans[o]+=pz[i-1]*digit[i];
    		for(int o=0;o<digit[i];o++) ans[o]+=pow[i-1];
    		ans[digit[i]]+=cp%pow[i-1]+1;
    		ans[0]-=pow[i-1];
    	}
    }
    

    [SCOI2009] windy 数

    题意简介:

    求区间 ([l,r]) 内相邻两个数字之差至少为 (2) 的正整数

    分析:

    也是一个比较裸的题目,但是本题前导 (0) 会影响答案,所以我们要增加一个前导 (0) 标记

    之后就是原样枚举,注意判断的时候要取绝对值

    #define fx(l,n) inline l n
    fx(int,dp)(int np,int pre,bool pz,bool limit){
    	if(!np) return 1;
    	if(!limit&&!pz&&dpv[np][pre]) return dpv[np][pre];
    	int al=limit?dig[np]:9,sum=0;
    	for(int i=0;i<=al;i++){
    		if((llabs(pre-i)<2)&&!pz) continue;
    		int po=dp(np-1,i,pz&&!i,limit&&(i==al));
    		sum+=po;
    	}
    	if(!limit&&!pz) dpv[np][pre]=sum;
    	return sum;
    }
    

    花神的数论题

    题意简介:

    ( ext{sum}(i)) 表示 (i) 的二进制中 (1) 的个数,求:

    [egin{align*} prodlimits^N_{i=1} ext{sum}(i) end{align*} ]

    分析:

    ( ext{num}(i))(1sim N) 中二进制中 (1) 的个数等于 (i) 的数的个数

    即:(prodlimits^N_{i=1} ext{sum}(i)=prodlimits_{i=1}i^{ ext{num}(i)})

    数位dp即可,代码较垃圾就不放了

    众所周知在 (n) 位二进制中,二进制中 (1) 的个数等于 (i) 的数的个数共有 (dbinom{n}{i})

    所以我们可以将递归转为枚举位

    for(int i=50,o;i!=-1;i--){
    	for(o=50;o;o--) num[o]+=num[o-1];
    	if((n>>i)&1){
    		num[high]+=1;
    		high+=1;
    	}
    }
    num[high]+=1;
    for(int i=1;i<=50;i++) (ans*=pow(i,num[i]))%=mod;
    

    Beautiful numbers

    题意简介:

    求出区间 ([l,r]) 内能被它每个数位上的数整除的数

    分析:

    首先我们知道 (1sim10) 的最小公倍数是 (2520),于是我们对于每层递归进行计算后 (\%2520) 即可

    我们还需统计当前已经选择的数的最小公倍数,这可以预处理直接拿来用

    之后我们在第 (0) 位判断余数是否整除,如果是 (0) 就统计

    暴力数位dp即可

    之后会TLE与MLE双丰收

    考虑如何优化,我们用来进行数位dp的数组有两维,即现在位数和当前递归层的模数已经不可优化了,考虑优化第三维

    我们发现有效的状态当且仅当在第 (0) 位的时候余数模最小公倍数为 (0)

    而这个最小公倍数一定是 (2520) 的一个因数

    而不管我们选择了哪些数,只要它们的最小公倍数相同,它们就一定是同一种状态

    我们成功的将第三维的大小优化至 (50)

    预处理 (2520) 的每一个因数,之后进行数位dp即可

    fx(int,dp)(int np,int amn,int q,bool lmt){
    	if(!np) return amn%q==0;
    	if(!lmt&&dpv[amn][mp[q]][np]!=-1) return dpv[amn][mp[q]][np];
    	int al=lmt?dig[np]:9,sum=0;
    	for(int i=0;i<=al;i++){
    		sum+=dp(np-1,(amn*10+i)%2520,i?(q*i/__gcd(q,i)):q,lmt&&i==al);
    	}
    	if(!lmt) dpv[amn][mp[q]][np]=sum;
    	return sum;
    }
    signed main(){
    	set(dpv,-1,dpv,1);
    	for(int i=1;i<=2520;i++) if(2520%i==0) mp[i]=++ys;
    }
    
  • 相关阅读:
    Hadoop出现 Wrong FS: hdfs://......错误的解决方法
    在Linux下安装JDK环境
    卸载Linux自带的JDK
    hadoop1.2.1伪分布模式安装教程
    spring配置bean的生命周期
    spring注入的四种方式
    python re模块search()与match()区别
    VB.NET操作Excel
    位运算
    Python简单源码解析
  • 原文地址:https://www.cnblogs.com/zythonc/p/14363825.html
Copyright © 2020-2023  润新知