数位统计/数位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

 

转载于:https://www.cnblogs.com/yefeng1627/archive/2013/05/17/3083543.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【优质项目推荐】 1、项目代码均经过严格本地测试,运行OK,确保功能稳定后才上传平台。可放心下载并立即投入使用,若遇到任何使用问题,随时欢迎私信反馈与沟通,博主会第一时间回复。 2、项目适用于计算机相关专业(如计科、信息安全、数据科学、人工智能、通信、物联网、自动化、电子信息等)的在校学生、专业教师,或企业员工,小白入门等都适用。 3、该项目不仅具有很高的学习借鉴价值,对于初学者来说,也是入门进阶的绝佳选择;当然也可以直接用于 毕设、课设、期末大作业或项目初期立项演示等。 3、开放创新:如果您有一定基础,且热爱探索钻研,可以在此代码基础上二次开发,进行修改、扩展,创造出属于自己的独特应用。 欢迎下载使用优质资源!欢迎借鉴使用,并欢迎学习交流,共同探索编程的无穷魅力! 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip 基于业务逻辑生成特征变量python实现源码+数据集+超详细注释.zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值