• 数位统计/数位DP 专题


    本质上是暴力模拟计算,逐位统计、以及如何寻找不被约束的状态来简化计算 是关键点。

    例题1 ural 1057.

      1. 树形结合, 按位统计. 若当前位为1, 则取0, 剩下的位任意取都比其小, ans += f[ L ][ k-tot ],  L表示剩下长度. 论文这个地方写的感觉不对- -...

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    
    int f[32][32];
    
    void init(){
        memset(f,0,sizeof(f));    
        f[0][0] = 1;
        for(int i = 1; i <= 31; i++){
            f[i][0] = f[i-1][0];
            for(int j = 1; j <= i; j++)
                f[i][j] = f[i-1][j-1] + f[i-1][j];
        }
    }
    int get(int n,int b){
        string s;
        while( n ){
            s = char((n%b)+'0') + s;
            n /= b;
        }
        for(int i = 0; i < (int)s.size(); i++){
            if( s[i]-'0' > 1 ){
                for(int j = i; j < (int)s.size(); j++)
                    s[j] = '1';
                break;    
            }    
        }
        int x = 0;
        for(int i = 0; i < (int)s.size(); i++)
            x = (x<<1)|(s[i]-'0');
        return x;
    }
    int calc(int n,int k){
        int tot = 0, res = 0;
        for(int i = 30; i >= 0; i--){
            if( (n&(1<<i)) ){
                if( tot <= k )    
                    res += f[i][(k-tot)];    
                tot += 1;    
            }    
        }    
        if( tot == k ) res++;
        return res; 
    }
    int main(){
        init();    
        int x, y, b, k;
        scanf("%d%d%d%d",&x,&y,&k,&b);
        x = get(x,b), y = get(y,b);
        printf("%d\n", calc(y,k) - calc(x-1, k) );
        return 0;
    }
    View Code

      2. 还是比较喜欢记忆化的写法, 比较通用.   Count( bool less, int Length, int cur_num, int target_num, int x ) 

    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std; 
    int get(int n,int b){
        string s;
        while( n ){
            s = char((n%b)+'0') + s;
            n /= b;
        }
        for(int i = 0; i < (int)s.size(); i++){
            if( s[i]-'0' > 1 ){
                for(int j = i; j < (int)s.size(); j++)
                    s[j] = '1';
                break;    
            }    
        }
        //printf("s = %s\n", s.c_str() );    
        int x = 0;
        for(int i = 0; i < (int)s.size(); i++)
            x = (x<<1)|(s[i]-'0');
        return x;
    } 
    int dp[50][50];
    // dfs( false, 31, 0, k, x )
    int dfs(bool less, int L, int tot, int k, long long x){ //x本身不被计算
        //printf("less = %d, L = %d, tot = %d, k = %d, x = %lld\n", less, L, tot, k, x );
        if( L == 0 )
            return less && (tot==k);
        if( less && (dp[L][k-tot]!=-1) ) 
            return dp[L][k-tot];
        int res = 0, d = (x&(1ll<<(L-1))) > 0 ? 1 : 0;    
        for(int i = 0; i <= 1; i++){
            if( (less==false) && (i > d) ) continue;    
            res += dfs( less||(i<d), L-1, tot + (i==1), k, x );    
        }    
        if(less) dp[L][k-tot] = res;
        return res;
    }
    void solve(int x,int y,int k){
        memset(dp,0xff,sizeof(dp));
        long long a = x, b = y+1; 
        printf("%d\n", dfs(false,32,0,k,b) - dfs(false,32,0,k,a) );
    }
    int main(){ 
        int x, y, b, k;
        scanf("%d%d%d%d",&x,&y,&k,&b);
        x = get(x,b), y = get(y,b);
        solve( x, y, k ); 
        return 0;
    }
    View Code

    [1,n] 区间中二进制形式下1的数量

    // If No Limit 
    // dp[i]: I位1的数量和
    // dp[i] = dp[i-1] + dp[i-1] + 2^(i-1)
    #include<cstdio>
    #include<cstring>
    const int N = int(1e5)+10;
    int f[N];
    int dfs(bool less,int x,int L){//32---1
    //    printf("L = %d, less = %d\n", L,less); getchar();getchar();
        if( L == 1 ) return less;
        if( less && (f[L]!=-1) ) return f[L];
    
        int res = 0, d = (x&(1<<(L-1)))?1:0;
        for(int j = 0; j < 2; j++){
            if( !less && (j>d) ) continue;
    
            res += dfs( less||(j<d), x, L-1);
            if( j == 1 ){
                if(less) res += 1<<(L-1);
                else{
                    for(int i = 1; i < L; i++)
                        if( x&(1<<(i-1)) ) res+= 1<<(i-1);
                }
            }
        }
        if( less ) f[L] = res;
        return res;
    }
    
    int main(){
        int x;
        memset(f,-1,sizeof(f));
    //    for(x = 1; x <= 1000; x++)
    //        printf("n = %d,res = %d\n", x, dfs(false,x,32) );
        while( scanf("%d", &x) != EOF){
            printf("%d\n", dfs(false,x,32) );
        }
        return 0;
    }
    View Code

    例题2 spoj 1182 Sorted bit sequence  求区间 [x,y] 中数位和从小到大,相同则数值大小排序的第K个数

      f( i, j ) 表示后i位任意,数位和为j的方案数 

      count(i,j) 表示 [1,i)间数位和为j的方案数  

      1. 首先利用 count() 求出第k的数位和 sum.

      2. 然后再逐位确定,同样利用count();

    负数可以选择 与整数分开,单独做, 或者通过转换例如在33位+1之类的 与整数一起做。

    例题3  spoj 2319 Sequence 

      因为对于一个最大值 S, 其分组数有一个最小值x, 若x<=M 则意味着S还可以减小。 所以可以二分找出最合适的S。

      对于每次二分得到的最大值S,求分数数量时,每次让分组尽量满,即可得到最小分组数量X

      函数 count( n ) 表示 [1,n] 中所有数二进制形式下 1的数量总和

      假定目前分组左边界是 a, 我们需要求最大的右边界满足 count(b) - count(a-1) <= S.

      这里需要通过逐位确定的方法的求解b的值

    例题4 sgu 390 Tickets

      sumOf( x ) 表示x的数位和

      最好理解成 按顺序枚举 x 属于 [L,R] , 然后一个一个分组添加。 若当前 数位和为 remain, 当加入x进来时, remain + sumOf(x) >= K时,此时就会多一个分组,

    否则当前未满的分组 remain = remain + sumOf(x). 

      我们可以提取一个不受约束的状态来 简化计算。 

      dp[ L ][ sum ][ left ]  :  表示 后L位取所有值,且L位之前的数位和为sum,且之前最后一个分组未满时已有的数位和left, 分组总数 与 最后一个未满分组剩余量

      然后进行按位处理以及合并状态。

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<algorithm>
    #include<cmath>
    using namespace std;
    
    typedef long long LL;
    const int Length = 19;
    
    int K ,low[20], high[20];
    
    void get( LL x, int *r ){
        int cnt = 0;
        for(int i = 0; i < Length; i++)
            r[i] = x%10, x /= 10;
    //    for(int i = 0; i < Length; i++)
    //        printf("%d ", r[i] ); puts("");
    }
    
    struct Node{
        int left; LL tot;
        Node(){}
        Node(int _l,LL _t):left(_l),tot(_t){}
    };
    Node dp[20][200][1001];
    bool vis[20][200][1001];
    
    Node count(bool isGreater,bool isLess,int L,int sum,int start){
        if( L == 0 ){
            if( isLess && isGreater ){
                if( start+sum >= K ) return Node(0,1);
                return Node(start+sum,0);
            }
            return Node(start,0);
        }
        int left = start; LL tot = 0;
        if( isLess && isGreater && vis[L][sum][left] ) return dp[L][sum][left];
        for(int x = 0; x < 10; x++){
            if( !isGreater && (x<low[L-1]) ) continue;
            if( !isLess && (x>high[L-1]) ) continue;
    
            Node t = count( isGreater||(x>low[L-1]),isLess||(x<high[L-1]),L-1,sum+x,left );
            tot += t.tot;
            left = t.left;
        }
        Node ret = Node(left,tot);
        if( isGreater && isLess ) dp[L][sum][start] = ret, vis[L][sum][start] = true;
        return ret;
    }
    int main(){
        LL L, R;
        while( scanf("%lld%lld%d",&L,&R,&K) != EOF ){
            get( L-1, low ); get( R+1, high );
            memset(vis,0,sizeof(vis));
            Node ans = count(false,false,Length,0,0);
            printf("%lld\n", ans.tot );
        }
        return 0;
    }
    View Code

    例题5 zoj 2599  Graduated Lexicographical Ordering

      将[1,n]中的所有数排序, 第一关键字是 数位和从小到大, 第二关键字是字典序。 

      求[1,n]中 值K在第几位, 以及第K位的值是多少。

      f[ L, sum ]  表示后L位任意,数位和为sum的方案数量。

      count( n, sum ) 表示 [1,n] 数位和为sum的方案数量.

      对于第一问,首先求出 {1,sumOf(K)-1} 的数量, 然后再统计 数位和为sumOf(k) 且字典序小于K的数量,相加即为结果。 对于统计数位和为 sum(k) 且 字典序小于K的数量, 因为长度不同,我们没法直接求。 我们可以枚举 x 的长度范围 [1, l ength(n) ] 来计算。

      \sum {  count( k, length, sum(k) ) - f( length-1, sum(K) ) }   d

      对于第二问也是利用上面的 方式来逐位确定 第K的数的每一位, 细节比较多。 

    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<iostream>
    #include<algorithm>
    using namespace std;
    
    typedef long long LL;
    const int Length = 19;
    
    LL n, K;
    
    LL f[20][200]; //后i位任意,数位和为j 的方案总数
    LL pow10[20];
    int getbit(LL x){
        for(int i = Length; i >= 1; i--){
            if( x/pow10[i-1] ) return i;
        }
    }
    int SumOf(LL x){
        int re = 0; while(x) re+=x%10,x/=10; return re;
    }
    int get(int *r,LL x){
        for(int i = 0; i < Length; i++) r[i]=x%10,x/=10;
    }
    void init(){
        pow10[0] = 1;
        for(int i = 1; i < Length; i++) pow10[i]=pow10[i-1]*10;
        memset(f,0,sizeof(f));
        f[0][0] = 1;
        for(int i = 1; i <= Length; i++){
            for(int j = 0; j < 200; j++){
                for(int x = 0; x<10 && x<=j; x++)
                    f[i][j] += f[i-1][j-x];
            }
        }
    }
    LL count(bool less, int *r, int L, int sum, int goal){ // [1,x) 数位和为goal的数量
        if( L == 0 ) return less && (sum==goal);
        if( less ) return f[L][goal-sum];
        LL res = 0;
        for(int x = 0; x < 10; x++){
            if( !less && (x>r[L-1]) ) continue;
            if( sum+x > goal ) continue;
            res += count(less||(x<r[L-1]),r,L-1,sum+x,goal);
        }
        return res;
    }
    LL Count(LL A, int L, int sum){ // 所有长度为L,数位和为sum, 且字典序小于A的前L位的数量
        LL t = A; bool flag = false;
        int r[20], k = getbit(t), tmp = 0;
        while( k > L ) t/=10,k--;
        while( k < L ){
            if( t<=n/10 && t*10<=n ) t*=10,k++;
            else{ t=n; flag=true; break; }
        }
    //    printf("Count:: t = %I64d\n", t);
        memset(r,0,sizeof(r));
        int len = 0; LL remain = t;
        while(t) r[len]=t%10,t/=10,tmp+=r[len++];
        LL temp = count(false,r,L,0,sum)-f[len-1][sum];
        if( (L <= getbit(A)) || (remain==n && flag) ) temp += (tmp==sum);
    //    printf("Count:: temp = %I64d\n", temp );
        return temp;
    }
    LL Deal_1(){
        LL tot = 0; int sum = SumOf(K);
        int r[20]; get(r,n+1);
        for(int i = 1; i < sum; i++ )
            tot += count(false,r,Length,0,i); //printf("Deal_1:: tot = %I64d\n", tot );
        for(int i = 1; i <= getbit(n); i++)
            tot += Count( K, i, sum );
    //    puts("--->");
        return tot;
    //    printf("%I64d\n", tot );
    }
    
    LL check(LL A,int x){
        if( A <= n/10 ) return min( A*10+x, n );
        else return n;
    }
    
    LL Deal_2(){
        LL rank = K, A; int sum, r[20]; get(r,n+1);
        for(sum=1;;sum++){
            LL tmp = count(false,r,Length,0,sum);
    //        printf("Deal:: tmp_%d = %I64d, rank = %I64d\n", sum, tmp, rank ); getchar(); getchar();
            if( rank - tmp > 0 ) rank -= tmp;
            else break;
        }
    //    printf("Deal_2:: rank = %I64d, sum = %d\n", rank, sum);
        int flag = 0;
        for(int i = 1; i <= 9; i++){
            LL tot = 0;
            for(int L = 1; L <= getbit(n); L++)
                tot += Count( i, L, sum );
    //        printf("Deal_2:: i = %d, tot = %I64d\n", i, tot );
            if( tot >= rank ){
                flag = 1;
                if(tot==rank && i==sum) A = i, rank = 0;
                else A = i-1;
                break;
            }
        }
        if( !flag ) A = 9;
        if( !rank ) return A;
    //    printf("First:: A = %I64d\n", A);
        for(int j,i = 2;i <= getbit(n);i++){ // 逐位确定
            LL tmp, x;
            for(j = 0; j < 10; j++){ //当前位的取值j
                tmp = 0, x = A;  // 计算当前位置取j时,长度相等字典序小于x,且数位和为sum的数量
                for(int L = 1; L <= getbit(n); L++) // 这里可能溢出
                    tmp += Count( x*10+j, L, sum);
    //            printf("Deal_2:: tmp = %I64d, j = %d\n", tmp, j );
                if( tmp >= rank ) break;
            }
            if( j < 10 ){
                if( (rank==tmp) && (SumOf(A*10+j)==sum) ) {A = A*10+j; break;}
                else A = A*10 + (j-1);
            }
            else A = A*10+9;
        }
        return A;
    }
    int main(){
    //    freopen("1.in","r",stdin);
        init(); // 预处理f(i,j) 后i位任意,且数量和为j的总数
        while( cin >> n >> K )
    //    while( scanf("%lld%lld",&n,&K) != EOF)
        {
            if( n+K == 0 ) break;
            LL a = Deal_1();
            LL b = Deal_2();
            cout << a << " " << b << endl;
    //        printf("%lld %lld", a,b );
        }
        return 0;
    }
    View Code
  • 相关阅读:
    [BZOJ 4117] Weather Report
    [BZOJ 3233] 找硬币
    集合迭代器Iterator
    如何实现数组与List的相互转换?在 Queue 中 poll()和 remove()有什么区别?哪些集合类是线程安全的?
    ArrayList分别与LinkedList、Vector、Array的区别
    HashMap与TreeMap
    HashSet原理
    并发场景下HashMap死循环导致CPU100%的问题
    HashMap工作原理
    HashMap与HashTable的区别
  • 原文地址:https://www.cnblogs.com/yefeng1627/p/3083543.html
Copyright © 2020-2023  润新知