zoukankan      html  css  js  c++  java
  • 数位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;
    }
    
  • 相关阅读:
    CodeForces gym Nasta Rabbara lct
    bzoj 4025 二分图 lct
    CodeForces 785E Anton and Permutation
    bzoj 3669 魔法森林
    模板汇总——快读 fread
    bzoj2049 Cave 洞穴勘测 lct
    bzoj 2002 弹飞绵羊 lct裸题
    HDU 6394 Tree 分块 || lct
    HDU 6364 Ringland
    nyoj221_Tree_subsequent_traversal
  • 原文地址:https://www.cnblogs.com/zythonc/p/14363825.html
Copyright © 2011-2022 走看看