之前做数位统计时,一般是先初始化,然后再逐位统计。
   前几天在codeforces遇到一种用记忆化搜索写的数位统计,挺神奇的。用它改写之前写过的几道数位统计,发现代码更短,速度也更快,有一定通用性.
   这种写法一般用于初始化跟逐位统计是一样过程的。
        
        codeforces 55D
        http://www.cppblog.com/Yuan/archive/2011/01/24/139201.html
      
        我是从这题了解到这种写法
             
        用这种方法写,一个流程是,列出式子(pre*10^pos + next)  pre是确定的,next是变量
      所以参数就为pre,pos加上一些其他的,还有一个标记doing,表示是计算有上界限制的还是没有上界限制(所有情况,即end=9)   
      dfs(pos-1 , npre , doing && i ==end)
      
      2010成都赛区A题  zoj 3416
      之前的写法 http://www.cppblog.com/Yuan/archive/2010/11/14/133608.html
      改写后
      

/**/ /*
    题目描述见另外一份
    平衡,即∑a[i]*(i-o) = 0   o为支点
    对于一个数,支点o是唯一的,所以不会有重复计数的情况(但有一种特殊的,就是0,00,000等都是一样的,会计算多次,
    最后减去即可)
    假设检查到pos处,对于上面的式子∑a[i]*(i-o) = 0,这里确定了支点为o
    之前的数其∑a[i]*(i-o)的结果为pre
    所以参数需要为pos , o , pre 

    当检查到pos=-1时,return pre == 0
    否则,看当前是计算所有情况还是具体情况(doing)
    如果是所有情况且dp值!=-1,直接return
    否则就枚举0到end
    
    而支点o需要在最外层枚举出来
*/

#include
< cstdio >
#include
< cstring >
#include
< algorithm >

using   namespace  std;

long   long  dp[ 19 ][ 19 ][ 2000 ];
int  digit[ 19 ];

void  init()
{
    memset(dp,
-1,sizeof(dp));
}


long   long  dfs( int  pos ,  int  o ,  int  pre ,  bool  doing)
{
    
if(pos == -1)
        
return pre == 0;

    
if(!doing && dp[pos][o][pre] != -1)
        
return dp[pos][o][pre];

    
long long ans = 0;
    
int end = doing ? digit[pos] : 9;
    
for(int i = 0 ; i <= end ; i ++)
    
{
        
int npre = pre;
        npre 
+= (pos-o)*i;
        ans 
+= dfs(pos-1 , o , npre , doing && i == end);
    }


    
if(!doing)
        dp[pos][o][pre] 
= ans;
    
return ans;
}


long   long  cal( long   long  x)
{
    
int pos = 0;
    
while(x)
    
{
        digit[pos
++= x % 10;
        x 
/= 10;         
    }

    
long long ans = 0;
    
for(int o = 0 ; o < pos ; o ++)
    
{
        ans 
+= dfs(pos-1 , o , 0 , 1);
    }

    
return  ans - (pos-1);//duplicate 0
}


int  main()
{
    init();
    
int T;
    
for(scanf("%d",&T) ; T--; )
    
{
        
long long left , right;
        scanf(
"%lld%lld",&left , &right);
        printf(
"%lld\n",cal(right) - cal(left - 1));
    }

    
return 0;
}



      2010 成都网络赛 B  hdu 3652
      之前的代码 http://www.cppblog.com/Yuan/archive/2010/09/23/127454.html
      

/**/ /*
    求[1,n]内有多少个数字,该数字有13,且能被13整除   n<=10^9
    
    x % 13 = 0
    (pre*10^pos + next) % 13 = 0   pre是之前确定的部分
    需要的参数为pre , pos , 状态have
    have记录pre拥有"13",pos+1位为"1",没有"13"   分别用have = 2,1,0表示
    
    然后记忆化搜索

*/

#include
< cstdio >
#include
< cstring >
#include
< algorithm >

using   namespace  std;

int  dp[ 10 ][ 13 ][ 3 ];
int  digit[ 10 ];

__int64 dfs(
int  pos ,  int  pre ,  int  have ,  bool  doing)
{
    
if(pos == -1)
        
return have == 2 && pre == 0;

    
if(!doing && dp[pos][pre][have] != -1)
        
return dp[pos][pre][have];

    
int ans = 0;
    
int end = doing ? digit[pos] : 9;
    
for(int i = 0 ; i <= end ; i ++)
    
{
        
int npre = (pre*10 + i) % 13;
        
int nhave = have;
        
if(have == 0 && i == 1)
            nhave 
= 1;
        
else if(have == 1 && i != 1)
            nhave 
= 0;
        
if(have == 1 && i == 3)
            nhave 
= 2;
        ans 
+= dfs(pos-1 , npre , nhave , doing && i == end );
    }


    
if(!doing)
        dp[pos][pre][have] 
= ans;
    
return ans;
}



int  cal( int  x)
{
    
int pos = 0;
    
while(x)
    
{
        digit[pos
++= x % 10;
        x 
/= 10;
    }

    
return dfs(pos - 1 , 0 , 0 , 1);
}


int  main()
{
    memset(dp,
-1,sizeof(dp));
    
int n;
    
while(~scanf("%d",&n))
        printf(
"%d\n",cal(n));
    
return 0;
}


 

      参数跟条件有关,就像hdu3555就不需要pre


         hdu 3555
       之前写法 http://www.cppblog.com/Yuan/archive/2010/10/24/131057.html
         

/**/ /*
    问[1,n]内有多少个数字包含"49"
    这里用记忆化搜索
    更加好写,而且快

    hdu 3652 是加强版
*/

#include
< cstdio >
#include
< algorithm >

using   namespace  std;


__int64 dp[
20 ][ 3 ];
int  digit[ 20 ];

__int64 dfs(
int  pos ,  int  have ,  bool  doing)
{
    
if(pos == -1)
        
return have == 2;

    
if(!doing && dp[pos][have] !=-1)
        
return dp[pos][have];

    __int64 ans 
= 0;
    
int end = doing ? digit[pos] : 9;
    
for(int i = 0 ; i <= end; i++)
    
{
        
int nhave = have;        
        
if(have == 1 && i != 4)
            nhave 
= 0;
        
if(have == 0 && i == 4)
            nhave 
= 1;
        
if(have == 1 && i == 9)
            nhave 
= 2;
        ans 
+= dfs(pos-1 , nhave , doing && i == end);
    }

    
    
if(!doing)
    
{
        dp[pos][have] 
= ans;
    }


    
return ans;
}


__int64 cal(__int64 x)
{
    
int pos = 0;
    
while(x)
    
{
        digit[pos
++= x % 10;
        x 
/= 10;
    }

    
return dfs(pos - 1  , 0 , 1);
}


int  main()
{
    memset(dp,
-1,sizeof(dp));
    
int T;
    
for(scanf("%d",&T) ; T-- ;)
    
{
            __int64 n;
            scanf(
"%I64d",&n);
            printf(
"%I64d\n",cal(n));
    }

    
return 0;
}