数位dp是与数位有关的区间统计 参考: 点击打开链接
思想是将数字拆分,dp[i][j] 长度为i的数字有多少个满足j条件的,从000~999
统计时,计当前数字为x,则其后面数字的变化的倍数是0~x-1 后面接000~999这样刚好统计不到这个数字n本身。
eg:
对于一个数,若其首位已经比询问上界小,则剩余位没有任何限制。此时如果能直接处理这一情况,则问题距离解决又会迈出一大步。
例如,在十进制下,计算[10000,54321]内的数字和,我们可以将其分解为:
[10000,19999],[20000,29999],[30000,39999],[40000,49999],[50000,54321]。
前四个区间如果可以直接解决,则只需处理最后一个区间,进一步将最后一个区间划分为:
[50000,50999],[51000,51999],[52000,52999],[53000,53999],[54000,54321]。
同理将最后一个区间划分下去,最后可以得到以下区间划分:
[10000,19999],[20000,29999],[30000,39999],[40000,49999],
[50000,50999],[51000,51999],[52000,52999],[53000,53999],
[54000,54099],[54100,54199],[54200,54299],
[54300,54309],[54310,54319],
[54320,54321]
一般有两种写法,自底向上和递归+记忆化搜索
自底向上要注意这种方法没有考虑n这个数字本身是否满足要求,所以统计区间 [m,n] 的计算是f(n+1)-f(m)
/* *********************************************** Author :bingone Created Time :2014-12-25 13:44:50 File Name :HDU3555.cpp ************************************************ */ #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <cmath> #include <cstdlib> #include <ctime> #define inf 0x3f3f3f3f #define maxn (10+1000) typedef long long ll; using namespace std; int M,T; ll N,dp[22][3]; int num[22]; ll solve() { ll res=0,cache=N; int tag=0,len=1; while(cache) num[len++]=cache%10,cache/=10; for(num[len--]=0;len;len--){ res+=num[len]*dp[len-1][2]; if(tag==1) {res+=num[len]*dp[len-1][0];continue;} if(num[len]>4) res+=dp[len-1][1]; if(num[len]==9 && num[len+1]==4) tag=1; } return res; } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); dp[0][0]=1; for(int i=1;i<22;i++){ dp[i][0]=dp[i-1][0]*10-dp[i-1][1]; dp[i][1]=dp[i-1][0]; dp[i][2]=dp[i-1][2]*10+dp[i-1][1]; } scanf("%d",&T); while(T--){ scanf("%I64d",&N);N++; printf("%I64d\n",solve()); } return 0; }
其实很多数位dp都是用dfs模板写
typedef long long LL; const int maxn=22; int dig[maxn]; LL f[maxn]/* [TODO] */; LL dfs(int pos,/* TODO */,int limit){ if (pos<0) return /* TODO */; if (!limit&&f[pos]/* [TODO] */!=-1) return f[pos]/* [TODO] */; LL res=0; int last=limit?dig[pos]:9; for (int i=0;i<=last;i++){ res+=dfs(pos-1,/* TODO */,limit&&(i==last)); } if (!limit) f[pos]/* [TODO] */=res; return res; } LL solve(LL n){ int len=0; while (n){ dig[len++]=n%10; n/=10; } return dfs(len-1,/* TODO */,1); }
于是参考别人的代码这个题有两种dfs
1.按照模板写的,pos表示当前处理到的位置,istrue记录数字处理位置之前的部分是否满足已经满足题意。
limit 该位是否有限制,比如n=1234,现在转移到12,如果下一位选3,那么再下一位就有上限, 上限为4,如果不选3,那么下一位就没限制,最高位9,转移能保证转移到数比n小
这样一来dfs的方法就可以处理n这个数字本身了,因为处理到pos==-1时候istrue已经表明状态。
LL dp[N][10][2]; LL dfs(int pos,int pre,int istrue,int limit) { if (pos < 0) return istrue; if (!limit && dp[pos][pre][istrue] != -1) return dp[pos][pre][istrue]; int last = limit ? dig[pos] : 9; LL ret = 0; for (int i = 0; i <= last; i++) { ret += dfs(pos-1,i,istrue || (pre == 4 && i == 9),limit && (i == last)); } if (!limit) { dp[pos][pre][istrue] = ret; } return ret; }
2.
dp[25][3]和递推方法的状态表达一样。
这个方式就很简单直观了
__int64 Dfs (int pos,int s,bool limit) //s为之前数字的状态 { if (pos==-1) return s==2; if (limit==false && ~dp[pos][s]) return dp[pos][s]; int i ,end=limit?bit[pos]:9; __int64 ans=0; for (i=0;i<=end;i++) { int nows=s; if(s==0 && i==4) nows=1; if(s==1 && i!=9) //前一位为4 nows=0; if(s==1 && i==4) nows=1; if(s==1 && i==9) //49 nows=2; ans+=Dfs(pos-1 , nows , limit && i==end); } //limit==true 则说明有限制,即所有可能并没有被全部记录 //limit==false则说明之后的分支状态已经搜索完全 return limit?ans:dp[pos][s]=ans; }
HDU 2089
/* *********************************************** Author :bingone Created Time :2014-12-24 9:57:30 File Name :HDU2089.cpp ************************************************ */ #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <vector> #include <queue> #include <set> #include <map> #include <string> #include <cmath> #include <cstdlib> #include <ctime> #define inf 0x3f3f3f3f #define maxn (10+1000) using namespace std; int N,M,T; int num[10][3]; int slove(int n) { int res=0; int we[10],tmp,wecnt; for(wecnt=1,tmp=n;tmp;wecnt++,tmp/=10) we[wecnt]=tmp%10; bool tag=false; for(we[wecnt--]=0;wecnt;wecnt--){ res+=we[wecnt]*num[wecnt-1][2]; if(tag==true){res+=we[wecnt]*num[wecnt-1][0];continue;} if(we[wecnt]>4) res+=num[wecnt-1][0]; if(we[wecnt]>6) res+=num[wecnt-1][1]; if(we[wecnt+1]==6 && we[wecnt]>2) res+=num[wecnt-1][0]; if(we[wecnt]==4 || (we[wecnt]==2 && we[wecnt+1]==6) ) tag=true; } return n-res; } int main() { //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); num[0][0]=1; for(int i=1;i<7;i++) /// jili jili and 2 notjili { num[i][0]=num[i-1][0]*9-num[i-1][1]; num[i][1]=num[i-1][0]; num[i][2]=num[i-1][2]*10+num[i-1][1]+num[i-1][0]; } while(~scanf("%d%d",&N,&M)){ if(N==0 && M==0) break; printf("%d\n",slove(M+1)-slove(N)); } return 0; }