HDU 6796 X Number

HDU 6796 X Number

在这里插入图片描述

input
3
1 10 1
1 11 1
1 100 0
output
1
2
1

数位DP+组合数学

题意:找到[l,r]符合条件[=d]的数字。[=d]定义为:当前数字中d出现的次数严格最大。e.g.111223为[*=1],112233为啥也不是
思路:典型的数位dp虽然想到了,,,但没想到组合数学
数位dp:dp[][][]三个维度分别表示当前还剩pos位,题意中的d,和一个长度为10的数组表示pos之前出现的每个数字的次数,这题不仅需要考虑limit还需要考虑前导0这个限制
组合数学:这个还是欠考虑,也不会,当在没有限制的情况下,比如1234XXX。这时不需要在dfs下去这种情况可以通过组合数学判断出来,方法如下:先判读至少还需要添加st个数字d。当前的情况数就是C(pos,st)*calc(pos-st,up)。
而calc计算的则是剩下n个位置,每个数字最多使用up次的种类数

ll calc(int n,int d,int up){
    mem(f,0);
    f[0][0]=1;
    for(int i=1;i<=10;i++){
        if(i==d+1) for(int j=0;j<=n;j++) f[i][j]=f[i-1][j];
        else
        for(int j=0;j<=n;j++){
            for(int k=0;k<=j;k++)
                if(j-k+arr[i-1]<=up)//新增填入j-k个数字[i-1],加上原有的需要<=限制数up
                    f[i][j]+=f[i-1][k]*C[j][j-k];//前i-1个数字填满k个位置,剩下的j-k个位置为组合数C(j,j-k)
        }
    }
    return f[10][n];
}

完整代码

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
#define debug(x) cout << #x << ": " << x << endl;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fcout cout<<setprecision(4)<<fixed
using namespace std;
typedef long long ll;
//======================================
namespace FastIO{
char print_f[105];void read() {}void print() {putchar('\n');}
template <typename T, typename... T2>
inline void read(T &x, T2 &... oth){x = 0;char ch = getchar();ll f = 1;while (!isdigit(ch)){if (ch == '-')f *= -1;ch = getchar();}while (isdigit(ch)){x = x * 10 + ch - 48;ch = getchar();}x *= f;read(oth...);}
template <typename T, typename... T2>
inline void print(T x, T2... oth){ll p3=-1;if(x<0) putchar('-'),x=-x;do{print_f[++p3] = x%10 + 48;}while(x/=10);while(p3>=0) putchar(print_f[p3--]);putchar(' ');print(oth...);}} // namespace FastIO
using FastIO::print;
using FastIO::read;
//======================================
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn = 1e6+5;
map<array<int,10>,ll>dp[20][20];
array<int,10>arr;
int a[25];
ll f[25][25],C[25][25];
int check(int d){
    for(int i=0;i<10;i++){
        if(i!=d&&arr[i]>=arr[d]) return 0;
    }
    return 1;
}
ll calc(int n,int d,int up){
    mem(f,0);
    f[0][0]=1;
    for(int i=1;i<=10;i++){
        if(i==d+1) for(int j=0;j<=n;j++) f[i][j]=f[i-1][j];
        else
        for(int j=0;j<=n;j++){
            for(int k=0;k<=j;k++)
                if(j-k+arr[i-1]<=up)//新增填入j-k个数字[i-1],加上原有的需要<=限制数up
                    f[i][j]+=f[i-1][k]*C[j][j-k];//前i-1个数字填满k个位置,剩下的j-k个位置为组合数C(j,j-k)
        }
    }
    return f[10][n];
}
ll dfs(int pos,int d,bool lead,bool limit){
    if(!limit&&!lead&&dp[pos][d].count(arr)) return dp[pos][d][arr];
    if(pos==0) return dp[pos][d][arr]=check(d);
    if(!lead&&!limit){
        int maxx=0;
        for(int i=0;i<10;i++) if(i!=d) maxx=max(maxx,arr[i]+1); //d需要大于等于maxx
        int st=max(maxx-arr[d],0);//pos后至少还需要st个d
        if(st>pos) return dp[pos][d][arr]=0;//需要add的d大于pos为0
        ll res=0;
        for(int i=st;i<=pos;i++){
            res+=C[pos][i]*calc(pos-i,d,arr[d]+i-1);
        }
        return dp[pos][d][arr]=res;
    }
    int up=limit?a[pos]:9;
    ll ans=0;
    for(int i=0;i<=up;i++){
        if(!(i==0&&lead)) arr[i]++;
        ans+=dfs(pos-1,d,lead && i==0,limit && i==a[pos]);
        if(!(i==0&&lead)) arr[i]--;
    }
    if(!limit&&!lead) dp[pos][d][arr]=ans;
    return ans;
}
ll solve(ll x,int d){
    int pos=0;
    while(x){
        a[++pos]=x%10;
        x/=10;
    }
    return dfs(pos,d,1,1);
}
int main() {
#ifndef ONLINE_JUDGE
    freopen("H:\\code\\in.in", "r", stdin);
    freopen("H:\\code\\out.out", "w", stdout);
    clock_t c1 = clock();
#endif
//**************************************
    for(int i=0;i<=20;i++) C[i][0]=1;
    for(int i=1;i<=20;i++){ //预处理组合数
        for(int j=1;j<=i;j++){
            C[i][j]=C[i-1][j]+C[i-1][j-1];  
        }
    }
    int T;
    read(T);
    while(T--){
        ll l,r,d;
        read(l,r,d);
        cout<<solve(r,d)-solve(l-1,d)<<"\n";
    }
//**************************************
    
#ifndef ONLINE_JUDGE
    cerr << "Time:" << clock() - c1 << "ms" << endl;
#endif
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值