• 数位dp小结


    数位dp

    对,你没有看错,我最近才会数位dp,我真是太菜了

    引入

    数位dp显著特点是按数位dp,也就是说按个位十位百位。通常解决 位与位之间有限制,或与位上的数相关的问题。还有个特点就是输入少

    通常问题格式为:在区间([l,r])内有多少个满足限制的数,(l,r<=10^{18})

    显然的结论为:区间([l,r])的答案为((区间[1,r]-区间[1,l-1]))

    问题转化为求区间([1,x])的答案(然鹅并没有什么卵用)

    求答案的方法即为按数位去dp计算答案。

    ……

    (f[i])表示在第(i)位时的答案,从(f[i-1])转移而来

    好像没什么好讲的

    讲一下细节:

    1.实现使用记忆化搜索会更简易

    2.一般从高位开始记忆化搜索,方便处理超出上界的情况

    例如:求([1,21])的数,十位不能取大于(2)的数,如果十位是(2),个位不能取大于(1)的数,否则个位取啥都行。

    实现时用(limit)记录(0或1)表示到当前状态之前数位有没有触碰上界

    3.看题目对前导(0)有没有什么限制,不一定每个题目都有

    给个不伪的伪代码理解一下:(仅供理解)

    //f[x][y]为前x位,状态为y(随题目改变)的答案
    
    int dfs(int x,int y,int st,int limit){
      
    	if(到底了)返回值;//if(!x) return 1;
      
    	if(已经记忆化过并且没有上限限制)返回当前状态值
        
    	int maxx=9;				//当前数位取值上限
    	long long sum=0;		//记录状态答案
    	if(limit==1)maxx=a[x];	//如果有上界,数位上限缩小
      
    	for(枚举数位值)
    		if(不符合限制)continue;  
    		if(前导0有特殊影响)sum+=dfs(x-1,更新状态,1,0);
    		else sum+=dfs(x-1,更新状态,0,是否还触碰上界);
        
      
    	if(!limit&&!st) f[x][y]=sum;	//如果有限制,说明前x位还有限制,不记忆化
    	return sum;	
    }
    
    int solve(long long x){
    	memset(f,-1,sizeof(f));len=0;
    	while(x)a[++len]=x%10,x/=10;	//拆位
    	return dfs(len,-2,1,1);
    }
    

    要点:理解清(limit)(st)限制

    还是要结合题目才行


    P2657[SCOI2009]windy数

    简单的数位dp练手题

    自己按模板打打看

    注意判前导(0)

    代码:相当于代码版的模板了

    #include<bits/stdc++.h>
    #define maxn 20
    using namespace std;
    long long a[maxn],n,m,len;
    long long f[maxn][maxn];
    int dfs(int x,int y,int st,int limit){
    	if(!x)return 1;		//到底了
    	if(f[x][y]!=-1&&limit==0)return f[x][y];	//记忆化过了且没有限制
    	int maxx=9;long long sum=0;
    	if(limit==1)maxx=a[x];	//更新数位值上限
    	for(int i=0;i<=maxx;i++){
    		if(abs(y-i)<2)continue;	//不符合限制
    		if(st==1&&i==0)sum+=dfs(x-1,-2,1,0);	//前导0处理
    		else sum+=dfs(x-1,i,0,limit&&i==a[x]);	//正常处理
    	}
    	if(!limit&&!st) f[x][y]=sum;	//没有限制记忆化
    	return sum;
    }
    int solve(long long x){
    	memset(f,-1,sizeof(f));len=0;
    	while(x)a[++len]=x%10,x/=10;
    	return dfs(len,-2,1,1);
    }
    int main(){
    	scanf("%lld%lld",&n,&m);
    	printf("%d
    ",solve(m)-solve(n-1));
    	return 0;
    }
    

    P4127[AHOI2009]同类分布

    题意:给出两个数(a,b),求出(a,b)中各位数字之和能整除原数的数的个数。

    不是位与位的限制了,而是原数与数位的限制了

    我们设(f[x][y][val])表示dp到前(x)位,此时数位和为(y),数的值为(val)的答案

    但很明显这是不可行的,(val)高达(10^{18}) 时空爆炸!

    我们观察,数位和只有([1,18*9]) ​种可能。

    那么我们可以考虑枚举数位和(sum),那么(val)变为在模(sum)意义下的数的值,(val)的范围便只有(162)这么大了,便可以数位dp了

    边界为当 (x=0)时,当前数位和(y==sum),数的值(val=0(mod sum))(1) 否则为 (0)

    long long dfs(int x,long long y,long long val,int limit,int mod){
    	if(!x&&y==0)return 0;			//特判0
    	if(!x) return val==0&&y==mod?1:0;	//边界
    	if(!limit&&f[x][y][val]!=-1)return f[x][y][val];	//记忆化
    	int maxx=9;long long res=0;
    	if(limit) maxx=a[x];
    	for(int i=0;i<=maxx;i++){
    		res+=dfs(x-1,y+i,(val*10+i)%mod,(limit&&i==maxx)?1:0,mod);
    	}
    	if(!limit)f[x][y][val]=res;
    	return res;
    }
    long long solve(long long x){
    	int len=0;long long ans=0;
    	while(x)a[++len]=x%10,x/=10;	
    	for(int i=1;i<=9*len;i++){	//枚举数位和
    		memset(f,-1,sizeof(f));
    		ans+=dfs(len,0,0,1,i);
    	}
    	return ans;
    }
    

    CF55DBeautiful numbers

    看大佬博客吧,我太懒了

    here T1,另强推并%%%

    放个代码

    #include<bits/stdc++.h>
    #define maxn 51
    using namespace std;
    int a[2530];
    long long f[20][2530][50],l,r;
    int Map[2530];
    long long dfs(int x,int y,int lcm,bool limit){
    	if(!x)return y%lcm==0;
    	if(f[x][y][Map[lcm]]!=-1&&!limit)return f[x][y][Map[lcm]];
    	int maxx=9;long long sum=0;
    	if(limit==1)maxx=a[x];
    	for(int i=0;i<=maxx;i++){
    		sum+=dfs(x-1,(y*10+i)%2520,lcm*max(i,1)/__gcd(lcm,max(i,1)),limit&&i==maxx);
    	}
    	if(!limit) f[x][y][Map[lcm]]=sum;
    	return sum;
    }
    long long solve(long long x){
    	int len=0;
    	if(!x)a[len=1]=0;
    	while(x)a[++len]=x%10,x/=10;
    	return dfs(len,0,1,1);
    }
    int main(){
    	memset(f,-1,sizeof(f));
    	int T,tot=0;
    	scanf("%d",&T);
    	for(int i=1;i<=2520;i++)if(2520%i==0)Map[i]=++tot;
    	while(T--){
    		scanf("%lld%lld",&l,&r);
    		printf("%lld
    ",solve(r)-solve(l-1));
    	}
    	return 0;
    }
    

    P4884 多少个1?

    To be continue……

  • 相关阅读:
    浅谈易语言“蓝屏”暗桩查找和处理方法
    解密入门教学(1-6)
    IOS之计算器实现
    瓦片地图与geoserver发布
    Mac使用入门
    postgres与osm初步使用
    python异步爬虫
    操作系统之死锁
    Android之IPC机制
    Android之WebView学习
  • 原文地址:https://www.cnblogs.com/hyfhaha/p/13648142.html
Copyright © 2020-2023  润新知