数位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 ; }
相关文章推荐
- 数位DP专题小结--by sgx 数位DP专题小结--by sgx
- 数位dp_小结
- 数位DP小结_记忆化搜索版
- 数位dp小结(状压的第一次试水)
- 数位DP专题小结--by sgx
- 数位dp小结
- 数位dp小结
- 数位DP小结
- 数位DP小结
- 基础数位DP小结
- 【算法笔记】数位dp小结
- 数位DP小结
- HDU 3555 数位DP入门(两种思路)
- UESTC 1307 windy数(数位DP)
- HDU 5898 数位DP
- ACdream 1064 完美数【数位DP】
- 【数位dp】【HDU 3555】【HDU 2089】【UESTC 1307】【CodeForces 258B】数位DP入门题
- [HDU 4507] 吉哥系列故事——恨7不成妻 数位dp
- 数位dp小记
- 数位DP入门之hdu 3652 B-number