数位DP/数位统计 初探

2 篇文章 0 订阅

一点废话:

本沙茶从昨天上午下午到现在一直看这鬼东西。。恶心死了啊有木有!!

以前做过一次windy数,代码调了一亿年...

经过两天的专题训练,数位dp一点从不会到入了一点门了吧。

以前是什么都不会,不会想也不会写。现在至少稍微能写一些大水题了。。

(其实还是什么都不会)

好了不说废话了。 


先说写法:

最开始我是写的预处理再循环,前几道题那样写挺不错,个人认为挺好的,后面也会放出这种代码。
做到后面的题问题就出来了,由于太沙茶而导致智商不够.. 然后看了很久记忆化DFS版,然后后面几道就写DFS version了。
主要是DFS比较无脑。。(其实差不多啦,,真的吗。。)

入门题目:

个人认为该顺序是由易至难的,都是网上或一些论文的一些经典入门题吧。
一些题网上题解很多我就不写了。(当然也只有我这种蒟蒻才看题解)
(一)Bomb
题目简述:求[a,b]之间含有"49"的数的个数。

预处理循环版:
#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
#define per(i,r,l) for (int i=r;i>=l;--i)
typedef long long LL;
LL f[20][3];
void Prep(){
	f[0][0]=1;
	rep(i,1,19){
		f[i][0]=9*f[i-1][0]+8*f[i-1][1];
		f[i][1]=f[i-1][0]+f[i-1][1];
		f[i][2]=f[i-1][1]+10*f[i-1][2];
		}
}
LL count(LL x){
	int len=0,a[22];LL res=0;
	bool exist=0;
	while (x) a[++len]=x%10,x/=10;a[len+1]=-1;
	per(i,len,1){
		res+=f[i-1][2]*a[i];//-1+1
		if (exist) res+=(f[i-1][0]+f[i-1][1])*a[i];
		if (!exist&&a[i]>4) res+=f[i-1][1];
		if (a[i+1]==4&&a[i]==9) exist=true;
		}
	if (exist) res++;
	return res;
}
LL n;int T;
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	Prep();
	scanf("%d",&T);
	while (T--){
		scanf("%I64d",&n);
		printf("%I64d\n",count(n));
		}
}


Dfs version:
#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
typedef long long LL;
LL f[25][4];int a[25];
LL dfs(int pos,int u,bool lim){
	if (pos<=0) return u==2;
	if (!lim&&f[pos][u]!=-1) return f[pos][u];
	LL res=0;
	int top=lim?a[pos]:9;
	rep(i,0,top){
		int v=u;
		if (u==1&&i==9) v=2;
		if (u==1&&i!=9&&i!=4) v=0;
		if (u==0&&i==4) v=1;
		res+=dfs(pos-1,v,lim&&i==top);
		}
	//printf("%d %d %d %d\n",pos,u,lim,res);
	return lim?res:(f[pos][u]=res);
}
LL count(LL x){
	int pos=0;
	while (x) a[++pos]=x%10,x/=10;a[pos+1]=-1;
	return dfs(pos,0,1);
}
int main(){
	freopen("a.in","r",stdin);
	freopen("c.out","w",stdout);
	memset(f,-1,sizeof f);
	int T;LL n;
	scanf("%d",&T);
	rep(i,1,T){
		scanf("%I64d",&n);
		printf("%I64d\n",count(n));
		}
}

(二)不要62
题目简述:[a,b]区间内没有“62”且没有“4”的数的个数。
和题一基本相同。。

#include <cstdio>
#include <algorithm>
#define per(i,r,l) for (int i=r;i>=l;--i)
#define rep(i,l,r) for (int i=l;i<=r;++i)
int f[10][10];
int pow[10];
void prep(){
	rep(i,0,9) f[1][i]=(i==4?0:1);
	pow[1]=1;
	rep(i,2,9) rep(j,0,9){
		pow[i]=pow[i-1]*10;
		rep(k,0,9)
			if (j!=4&&!(j==6&&k==2)) f[i][j]+=f[i-1][k];
		}
}
int count(int x){
	int res=0,len=1,u=-1,v=-1;
	while (x>=10*pow[len]) len++;
	per(i,len,1){
		int u=x/pow[i];
		x-=u*pow[i];
		rep(j,0,u-1) if (!(v==6&&j==2))res+=f[i][j];
		if ((v==6&&u==2)||u==4) break;
		if (i==1) res++;
		v=u;
		}
	return res;
}
int n,m;
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	prep();
	while (scanf("%d%d",&n,&m)!=EOF){
		if (n+m==0) return 0;
		printf("%d\n",count(m)-count(n-1));
		}
}

(三)windy数
题目描述:不含前导零且相邻两个数字之差至少为2的正整数被称为windy数,求[a,b]之间有多少windy数。
这代码是五十年前写的,可以对比一下发现以前写的是多么丑。。(虽然现在仍然很丑~)
/*
	f[i][j]->第i位为j之内的个数
	f[i][j]=Sum{f[i-1][k]} abs(j-k)>=2
*/
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
LL Base[12];
LL f[12][10];//f[i][j]->位数i 最高位为j 的个数
void preW()
{
	Base[1]=1;
	for (int i=2;i<=11;++i)Base[i]=10*Base[i-1];
	for (int i=0;i<=9;++i) f[1][i]=1; 
	for (int i=2;i<=11;++i)
	 for (int j=0;j<=9;++j)
	  for (int k=0;k<=9;++k)
		if (abs(j-k)>=2) f[i][j]+=f[i-1][k];
}
LL Count(LL n)
{
	if (n==0) return 0;
	LL ret=0;
	int p=1;
	while (Base[p+1]<=n){
		for (int i=1;i<=9;++i)
			ret+=f[p][i];
		p++;
		}
	int pre=n/Base[p];
	for (int i=1;i<=pre-1;++i)
		ret+=f[p][i];
	if (p==1) return ret+1;//本身也是
	n-=pre*Base[p];
	int now=n/Base[p-1];
	for (int i=p-1;i>=1;--i){
		for (int j=0;j<now;++j)
			if (abs(j-pre)>=2)
				ret+=f[i][j];
		if (abs(pre-now)<2) break;//后面的都不能
		if (i!=1){
			pre=now;
			n-=pre*Base[i];
			now=n/Base[i-1];
			}
		else
			ret++; //i==1时abs(pre-now)<2 -->自己也是
		}
	return ret;
}
int main(){
	preW();
	LL n,m;
	scanf("%lld%lld",&n,&m);
	printf("%lld\n",Count(m)-Count(n-1));
}

(四)B-number
题目描述:求[a,b]之间被13整除且含有“13”的数的个数。

给跪了,这题调了一晚上,还是没过,第二天早上一眼发现晚上交的全都没删freopen!!!!!
#include <cstdio>
#include <algorithm>
#define per(i,r,l) for (int i=r;i>=l;--i)
#define rep(i,l,r) for (int i=l;i<=r;++i)
typedef long long LL;
int f[12][10][14][2];
LL pow[12];
void Preprocess(){
	f[0][0][0][0]=1;pow[1]=1;
	rep(i,2,11) pow[i]=pow[i-1]*10;
	rep(i,1,10) rep(j,0,9){
		rep(k,0,9) rep(mod,0,12){
			if (j==1&&k==3){
				f[i][j][((LL)j*pow[i]+mod)%13][1]+=f[i-1][k][mod][0]+f[i-1][k][mod][1];
			}else{
				f[i][j][((LL)j*pow[i]+mod)%13][0]+=f[i-1][k][mod][0];
				f[i][j][((LL)j*pow[i]+mod)%13][1]+=f[i-1][k][mod][1];
				}
			}
		}
}
int count(int x){
	int res=0,mod=0,len=1,v=-1,u=-1;bool exist13=0;
	while(x>=pow[len+1]) len++;
	per(i,len,1){
		u=x/pow[i];x-=u*pow[i];
		rep(j,0,u-1){
			res+=f[i][j][(13-mod)%13][1];
			if (exist13||(v==1&&j==3)){
				res+=f[i][j][(13-mod)%13][0];
				}
			}
		if (v==1&&u==3)exist13=true;//!!
		mod=(u*pow[i]+mod)%13;
		if (i==1&&exist13&&mod==0) res++;
		v=u;
		}
	return res;
}
int n;
int main(){
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	Preprocess();
	while (scanf("%d",&n)!=EOF)
		printf("%d\n",count(n));
}

(五)Amount of Degrees
题目描述:求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K个互不相等的B的整数次幂之和。

#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
#define per(i,r,l) for (int i=r;i>=l;--i)
int f[33][33];
void Prep(){
	f[0][0]=1;
	rep(i,1,31){
		f[i][0]=1;
		rep(j,1,i) f[i][j]=f[i-1][j]+f[i-1][j-1];
		}
}
int count(int x,int k,int b){
	int len=0,res=0,a[33];
	while (x) a[++len]=x%b,x/=b;
	per(i,len,1){
		if (a[i]==1) res+=f[i-1][k--];
		if (a[i]>1){res+=f[i-1][k]+f[i-1][k-1];break;}
		if (k==0) res++;
		if (k<=0) break;
		}
	return res;
}
int x,y,k,b;
int main(){
	Prep();
	while(scanf("%d%d%d%d",&x,&y,&k,&b)!=EOF)
		printf("%d\n",count(y,k,b)-count(x-1,k,b));
}


(六)self 同类分布
题目简述:给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数。

这道题我是枚举数字的和,每一个和分别重新处理一次,要不然不知道他模多少。还有要MLE。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)
typedef long long LL;
LL f[20][173][173];int MOD;
LL pow[20];
LL dfs(int pos,int sum,int mod,bool lim,int a[]){
	if (pos*9<sum) return 0;
	if (pos<=0) return sum==0&&mod==0;
	if (!lim&&~f[pos][sum][mod]) return f[pos][sum][mod];
	int top=lim?a[pos]:9;LL res=0;
	rep(i,0,top){
		int s=sum-i,m=(int)(((LL)mod+(LL)i*pow[pos])%(LL)MOD);
		if (s<0) break;
		res+=dfs(pos-1,s,m,lim&&i==top,a);
		}
	return lim?res:(f[pos][sum][mod]=res);
}
int a[20],b[20]; 
LL count(LL x,LL y){
	int lena=0,lenb=0;LL res=0;
	while (y) b[++lenb]=y%10,y/=10;
	while (x) a[++lena]=x%10,x/=10;
	for (int i=1;i<=lenb*9;++i){
		memset(f,-1,sizeof f);MOD=i;
		res+=dfs(lenb,i,0,1,b);
		res-=dfs(lena,i,0,1,a);
		}
	return res;
}
LL x,y;
int main(){
	pow[1]=1;
	rep(i,2,19) pow[i]=pow[i-1]*10;
	scanf("%lld%lld",&x,&y);
	printf("%lld\n",count(x-1,y));
}

小结:

是不是大水题。。
数位DP有两大问题吧,
1.如何找状态。
2.如何写。
第二个问题其实不是什么问题,多写几个代码就会了。
第一个问题就比较难了。(像我这种智商真心拙计的蒟蒻啊,只能跪跪跪跪跪了~)
找状态要抓住问题的特征分析吧,相当于对数字完整分类,记录关键信息,同时这个又要能够递推才行,不然不就相当于深搜么。。
有些题可能要注意前导零什么的。。


循环和dfs记录的状态是有区别的:
循环f[i][j]记录的是从低位到第i位状态是balabala时的值。
记忆化dfs记录的是从高位到第i位的状态是balalba时的值。

好像真的两种其实真的没有太大的区别的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值