一点废话:
本沙茶从昨天上午下午到现在一直看这鬼东西。。恶心死了啊有木有!!
以前做过一次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时的值。
好像真的两种其实真的没有太大的区别的。