2020杭电多校第三场 1006 - X Number (HDU 6796 数位dp)

X Number

Time Limit: 3000/3000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 805    Accepted Submission(s): 327


 

Problem Description
Teitoku loves many different kinds of numbers, and today Little W wants him to classify some positive integers into different categories.

There are 11 categories, numbered from 0 to 10. For each positive integer x, if there exists only one type of digit d (0≤d≤9) that occurs in x with the highest frequency, then we say x should be classified into category d, or otherwise, in case such digit doesn't exist, we say x should be classified into category 10.

For example,

    ● 111223 should be classified into category 1 since digit 1 occurs three times, and digits 2 and 3 occur less than three times respectively, and
    ● 3345544 should be classified into category 4 since digit 4 occurs three times, and digits 3 and 5 occur less than three times respectively, and
    ● 112233 should be classified into category 10 since digits 1, 2 and 3 occur twice respectively.

Little W doesn't care about category 10 and he just wants Teitoku to tell him the number of integers ranged from l to r that should be classified to another category d. However, Teitoku can hardly solve this problem, so he asks you for help.
 

 

Input
There are several test cases.

The first line contains an integer T (1≤T≤1000), denoting the number of test cases. Then follow all the test cases.

For each test case, the only line contains three integers l, r and d (1≤l≤r≤1018,0≤d≤9), representing a problem.
 

 

Output
For each test case, output in one line the number of integers ranged from l to r that should be classified to category d.
 

 

Sample Input
 
3 1 10 1 1 11 1 1 100 0
 

 

Sample Output
 
1 2 1
Hint

For the sample cases, 1 and 11 are in category 1, 100 is in category 0 and 10 is in category 10.

题意:

给你三个整数l,r,d(1<=l<=r<=1e18,1<=d<=9),求在区间[l,r]中,有多少个数字,其数位上数码d出现的次数严格最多(不包含前导0)。

思路:

一看这个数据范围还有问题显然是数位dp。

但是这题,我们显然状态设计中不能直接存储0~9每个数字出现的次数。

考虑limit特性,当前导0去掉后,并且没有limit限制,那么后面的数位都可以填0~9任意一个数字。

那么我们就可以从这里下手,考虑如何快速计算出没有前导0和limit限制下的答案。

考虑枚举数码d最终出现了num次,其他数码i已经出现了cnt[i]次,然后我们就可以对每个num做一个dp,dp[i][j]表示填到第i个数码,剩下的位置中已经填了j个数字的答案总数(这样就能枚举当前数码i填了剩下的位置中k个位置,用组合数转移一下)。

这样每次都累加答案,就可以快速计算出没有限制时的答案。至于边界条件,留给读者自行推导。

状态转移公式:

代码:

#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define ls (rt<<1)
#define rs (rt<<1|1)
using namespace std;
const int maxn=4e1+5;
//const double pi=acos(-1.0);
//const double eps=1e-9;
//const ll mo=1e9+7;
ll l, r, d;
int a[maxn];
template <typename T>
inline void read(T &X){
    X=0;int w=0; char ch=0;
    while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
    while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
    if(w) X=-X;
}
ll dp[22][22];
ll c[maxn][maxn];
int cnt[maxn];
ll dfs(int pos,int lmt,int qd){
    if(pos==-1) {
        rep(i,0,9) {
            if(i!=d&&cnt[i]>=cnt[d])
                return 0;
        }
        return 1;
    }
    if(!lmt&&!qd){
        ll ans = 0;
        int mx = cnt[d];
        rep(i, 0, 9) if (i != d) mx = max(mx, cnt[i] + 1);
        for (int num = mx; num <= cnt[d] + pos + 1; num++){//cnt[d]
            memset(dp, 0, sizeof(dp));//
            dp[0][0] = 1;//
            for (int i = 1; i <= 10;i++){
                if(i-1==d){
                    rep(j, 0, 20) dp[i][j] = dp[i - 1][j];
                    continue;
                }
                for (int j = 0; j <= cnt[d] + pos + 1 - num;j++){
                    for (int k = 0; k <= j && k <= num - cnt[i - 1] - 1; k++){
                        dp[i][j] += dp[i - 1][j - k] * c[pos + 1 - (j - k)][k];
                    }
                }
            }
            ans += dp[10][cnt[d] + pos + 1 - num];
        }
        return ans;
    }
    int up = lmt ? a[pos] : 9;
    ll ans = 0;
    rep(i,0,up){
        if(!qd||i)
            cnt[i]++;
        ans += dfs(pos - 1, lmt && i == a[pos], qd && i == 0);
        if(!qd||i)
            cnt[i]--;
    }
    return ans;
}
ll cal(ll x){
    int len = 0;
    while(x){
        a[len++] = x % 10;
        x /= 10;
    }
    return dfs(len - 1, 1, 1);
}
void solve(){
    read(l);
    read(r);
    read(d);
    ll ans = cal(r) - cal(l - 1);
    printf("%lld\n", ans);
}
int main(){
/*
#ifdef ONLINE_JUDGE
#else
    freopen("D:/Temp/in.txt", "r", stdin);
#endif
*/
    // freopen("e://duipai//myout.txt","w",stdout);
    int T=1,cas=1;
    read(T);
    c[0][0] = 1;
    rep(i,1,25){
        c[i][0] = 1;
        rep(j, 1, i) c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
    }
    //cout << c[4][2] << endl;
    while(T--){
        solve();
    }
    system("pause");
    return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值