The Counting Problem UVA - 1640 数位dp

问题

分析

统计1-a,1-b的中的数字个数,然后相减
数位dp

//数位dp
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <vector>
#include <utility>
using namespace std;
typedef long long ll;
const int maxn=15;
ll le,ri,Ans[2][maxn],a[maxn],num[maxn],cnt[maxn],head[maxn];

//以54321为例
void solve(ll x,ll *ans){
    int len=0,x2=x;
    while(x){
        a[++len]=x%10;
        x=x/10;
    }
    num[1]=a[1];
    for(int i=2;i<=len;++i){
        num[i]=a[i]*head[i]+num[i-1];
    }
    //统计有前导0的i位数  i<len  0-9999
    ans[0]=1;
    for(int i=1;i<len;++i){
        ans[0]+=9*cnt[i-1];
        for(int j=1;j<10;++j){
            ans[j]+=head[i]+9*cnt[i-1];
        }
    }
    //统计没有前导0的,且i<a[len]的数  1000-49999
    for(int i=1;i<a[len];++i){
        ans[i]+=head[len];
    }
    for(int j=0;j<10;++j){
        ans[j]+=(a[len]-1)*cnt[len-1];
    }
    if(x2!=0) ans[a[len]]+=(num[len-1]+1);

    //50000-54321中的0000-4321
    for(int i=len-1;i>0;--i){
        for(int j=0;j<a[i];++j){
            ans[j]+=head[i];
        }
        for(int j=0;j<10;++j) ans[j]+=a[i]*cnt[i-1];
        ans[a[i]]+=(num[i-1]+1);
    }
}

int main(void){
    head[1]=1;
    cnt[1]=1;
    for(int i=2;i<maxn;++i){
        head[i]=10*head[i-1];  //首部非0的i位数有多少个
        cnt[i]=i*head[i];    //首部非0的i+1位数去除第一个数字剩下i的数字(无限制)的出现次数
    }
    while(scanf("%lld%lld",&le,&ri)==2 && (le || ri)){
        memset(Ans,0,sizeof(Ans));
        if(le>ri) swap(le,ri);
        solve(ri,Ans[0]);
        solve(le-1,Ans[1]);
        for(int i=0;i<10;++i){
            if(i) printf(" ");
            printf("%lld",Ans[0][i]-Ans[1][i]);
        }
        printf("\n");
    }
    return 0;
}

//数位dp

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <vector>
#include <utility>
using namespace std;
typedef long long ll;
const int maxn=10;
struct Digits{
    ll d[10],cur;
    Digits(){
        cur=0;
        memset(d,0,sizeof(d));
    }
    Digits(ll cur){
        this->cur=cur;
        memset(d,0,sizeof(d));
    }
    Digits operator + (const Digits &rhs){
        Digits temp;
        for(int i=0;i<10;++i){
            temp.d[i]=d[i]+rhs.d[i];
        }
        temp.cur=cur+rhs.cur;
        return temp;
    }
    Digits& operator += (const Digits &rhs){
        for(int i=0;i<10;++i){
            d[i]+=rhs.d[i];
        }
        cur+=rhs.cur;
        return *this;
    }
};
ll le,ri;
// dp[pos][lead]是位数是pos,前导是lead,并且不是lim的状态,存储pos以后位的数字数量
int ans[maxn],a[maxn],vis[maxn][2];
Digits dp[maxn][2];

//pos是现在的位置,lead是是否有前导0,lim是代表数位的取值是否有上界
Digits dfs(int pos,int lead,bool lim){
    if(pos==0) return Digits(1);  //现在pos位的数字
    if(!lim && dp[pos][lead].cur) return dp[pos][lead];
    int up=(lim)?a[pos]:9;
    Digits temp;
    for(int i=0;i<=up;++i){
        Digits t=dfs(pos-1,lead&&(i==0),lim&&(i==up));
        temp+=t;
        if(!lead || (lead&&i)) temp.d[i]+=t.cur; //pos位数字数,非前导0
    }
    if(!lim) dp[pos][lead]=temp;
    return temp;
}

void solve(ll x,int d){
    for(int i=0;i<maxn;++i){
        for(int j=0;j<2;++j) dp[i][j].cur=0;
    }
    int pos=0;
    while(x){
        a[++pos]=x%10;
        x=x/10;
    }
    Digits t=dfs(pos,1,true);
    for(int i=0;i<10;++i) ans[i]+=d*t.d[i];
}

int main(void){
    while(scanf("%lld%lld",&le,&ri)==2 && (le || ri)){
        memset(ans,0,sizeof(ans));
        if(le>ri) swap(le,ri);
        solve(ri,1);
        solve(le-1,-1);
        for(int i=0;i<10;++i){
            if(i) printf(" ");
            printf("%lld",ans[i]);
        }
        printf("\n");
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值