您的位置:首页 > 其它

数位DP小结(阶段性)

2017-10-02 22:19 232 查看

写在前面

感觉写多了和模板似的……

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 ;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: