写在前面
感觉写多了和模板似的……
UPD at 2018.3.8:其实一点也不模板,如果觉得数位dp很水的话,参见[SCOI2014]方伯伯的商场之旅
long long/*(或者int)*/ dfs ( int len, ...... ,limit)
if( !limit && dp != -1 ) return dp
int lim = limit ? W[len] : 9/*(这里依据题意,比如二进制dp这里就是1)*/
long long rt = 0 ;
for( int i = /*下限*/ ; i <= lim ; i ++ ){
/*题目所需判断*/
rt += dfs( len-1 , ...... , limit && i == lim ) ;
}
return limit ? rt : dp = rt ;
中间的根据题意写就好了…
什么是数位DP?
数位DP就是用DP去解决一些数字统计问题
比如说最经典的问题:
给定[L,R],询问这个区间内不含有”49”子串的数字的个数
上一位如果取了4,这一位就不能取9
于是定义dp[i]为长度为在i位及以下时,不含49的数字的个数
那么函数参数”……”里的东西就是bool is_four
“/* 题目所需判断 */”里就是if( is_four && i == 9 ) continue ;
递归的dfs里的”……”就是 i==4
然后这道题就做完了qwq
一些题
HDU2089
题意
求出给定[L,R]中,不含”62”和”4”的数字的个数
题解
= =这个题和上面说的不含49差不多,只需要再加一个i==4就continue即可
自带大常数的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , M , dp[25][2] , W[25] ;
int dfs( int len , int sta , bool limit ){
if( len == 0 ) return 1 ;
if( !limit && dp[len][sta] != -1 ) return dp[len][sta] ;
int rt = 0 ;
for( int i = 0 ; i <= ( limit ? W[len] : 9 ) ; i ++ ){
if( i == 4 ) continue ;//4不合法,因此直接continue
if( sta && i == 2 ) continue ;//如果前一个是6并且当前2,仍然continue
rt += dfs( len-1 , i == 6 , limit && i == W[len] ) ;
}
return limit ? rt : dp[len][sta] = rt ;
}
int solve( int Emm ){
memset( W , 0 , sizeof( W ) ) ;
int x = 0 ;
while( Emm ){
W[++x] = Emm %10 ;
Emm /= 10 ;
}
return dfs( x , 0 , true ) ;//因为是从高位向低位走,因此一开始limit为true
}
int main(){
memset( dp , -1 , sizeof( dp ) ) ;
while( scanf( "%d%d" , &N , &M ) && N && M ){
printf( "%d\n" , solve( M ) - solve( N - 1 ) ) ;
}
}
HDU4734
题意
对于一个十进制数,定义它的某一种权值f(x)为
他的十进制的第i为乘以2^(i-1)的和
比如1234,他的权值为1*2^3 + 2*2^2 + 3*2^1 + 4*2^0
给定A,B,求出f(x)<=f(A)并且0<=x<=B的数字的个数
题解
先算出f(A),然后传参到dfs。在dfs中,每选一位的数就剪掉一位的权值,如果选到最后剩余的权值大于等于0,说明这个数字是可以的,统计答案,完了。其余的也几乎是板子。
自带大常数的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int T , A , B , dp[12][5005] , W[12];
int dfs( int len , int left , bool limit ){
//printf( "(dfs ) len = %d left = %d limit = %d\n" , len , left , limit ) ;
if( len == 0 ) return left >= 0 ;//已经选完了,left还有剩或者刚刚好为0
if( !limit && dp[len][left] != -1 ) return dp[len][left] ;//记忆化,注意只有!limit才能记忆
//剩下的都是普通操作
int lim = ( limit ? W[len] : 9 ) , rt = 0 ;
for( int i = 0 ; i <= lim ; i ++ ){
int nxt_left = left - i * ( 1<<(len-1) ) ;
if( nxt_left < 0 ) break ;
rt += dfs( len-1 , nxt_left , limit && ( i == lim ) ) ;
}
return limit ? rt : dp[len][left] = rt ;
}
int dvd( int b ){
int x = 0 , rt = 0 ;
while( b ){
rt += ( b % 10 ) * ( 1 << x ) ;
b /= 10 ; x ++ ;
}
//printf( "(div ) rt == %d\n" , rt ) ;
return rt ;
}
int solve(){
int cnt = 0 ;
while( B ){
W[++cnt] = B%10 ;
B /= 10 ;
}
return dfs( cnt , dvd( A ) , true ) ;
}
int main(){
memset( dp , -1 , sizeof( dp ) ) ;
scanf( "%d" , &T ) ;
for( int Emmmmm = 1 ; Emmmmm <= T ; Emmmmm ++ ){
scanf( "%d%d" , &A , &B ) ;
printf( "Case #%d: %d\n" , Emmmmm , solve() ) ;
}
}
HDU3709
题意
对于一个十进制数,我们给他指定一个支点,那么我们说这个数是平衡的,当且仅当他左边的权重等于右边的权重
举个栗子
12348,当支点为4时
左边的权值:1*3+2*2+3*1
右边的权值:8*1
因此这是一个平衡数
题解
很容易证明,一个非0的数字,如果他是平衡数,那么它的支点有且仅有一个,因为把这个支点往左或者往右都一定不可能再次平衡
然后我们就去枚举每个位置作为支点,最后求个和就好了
注意0这个数,无论支点在哪里都是平衡数,因此需要剪掉重复的
其他好像没有什么需要注意的地方
自带大常数的代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int T , W[20] ;
long long dp[20][20][2000] , x , y ;
long long dfs( int len , int mid , int sum , int limit ){
if( len == 0 ) return sum == 0 ;
if( !limit && dp[len][mid][sum] != -1 ) return dp[len][mid][sum] ;
int lim = ( limit ? W[len] : 9 ) ;
long long rt = 0 ;
for( int i = 0 ; i <= lim ; i ++ )
rt += dfs( len - 1 , mid , sum + i * ( mid - len ) , limit && i == lim ) ;
return limit ? rt : dp[len][mid][sum] = rt ;
}
long long solve( long long x ){
if( x < 0 ) return 0 ;
int cnt = 0 ;
while( x ){
W[++cnt] = x%10 ;
x /= 10 ;
}
//printf( "(solve ) cnt = %d\n" , cnt ) ;
long long rt = 0 ;
for( int i = 1 ; i<= cnt ; i ++ )
rt += dfs( cnt , i , 0 , true ) ;
return rt - cnt + 1 ;
}
int main(){
memset( dp , -1 , sizeof( dp ) ) ;
scanf( "%d" , &T ) ;
while( T-- ){
scanf( "%lld%lld" , &x , &y ) ;
printf( "%lld\n" , solve( y ) - solve( x - 1 ) );
}
}
POJ3252
题意
求出给定[L,R]区间内的符合下列条件的数的个数
1,将该数写成二进制形式,0的个数不少于1
题解
这个题嘛,二进制的数位DP
注意的是这个题需要对前导0进行特殊判定
其他的好像也没什么
代码比较丑
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int st , ed , dp[35][70] , W[35] ;
int dfs( int len , int Dv , bool limit , bool lead ){
if( len == 0 ) return Dv >= 32 ;
if( !limit && !lead && dp[len][Dv] != -1 ) return dp[len][Dv] ;
int rt = 0 ;
rt += dfs( len-1 , Dv + ( lead ? 0 : 1 ) , limit && W[len] == 0 , lead ) ;//取0
if( W[len] == 1 || !limit ) rt += dfs( len - 1 , Dv - 1 , limit && W[len] == 1 , false ) ;//取1
if( !limit && !lead ) dp[len][Dv] = rt ;
return rt ;
}
int solve( int x ){
memset ( W , 0 , sizeof( W ) ) ;
int cnt = 1 ;
while( x ){
if( x&1 ) W[cnt] = 1 ;
x >>= 1 ; ++cnt ;
}
return dfs( cnt - 1 , 32 , true , true ) ;
}
int main(){
memset( dp , -1 , sizeof( dp ) ) ;
while( scanf( "%d%d" , &st , &ed ) != EOF )
printf( "%d\n" , solve( ed ) - solve( st - 1 ) ) ;
return 0 ;
}