2020 Multi-University Training Contest 3---- HDU--6796、X Number(dfs+计数类dp)

博客介绍了如何解决一个数学计数问题,即在给定区间[l, r]内找到数位众数为d且唯一的数字个数。通过深度优先搜索(DFS)和动态规划(DP)相结合的方法,统计每个数位的出现次数,并计算满足条件的数字数量。文章提供了官方题解思路、代码实现和复杂度分析。" 5914659,878061,如何从drawable创建Bitmap并调整大小,"['Android开发', '图像处理', 'Bitmap操作', 'Canvas绘图', '资源管理']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

题面:
在这里插入图片描述

题意:
求区间 [ l , r ] [l,r] [l,r]中的所有数,其数位众数为 d d d,且数位众数唯一的数的个数。

题解:
先看一下官方题解:
在这里插入图片描述

我们顺着官方题解的思路,如果当前没有前导0且数位取值没有限制,那么就说明剩下的数位可以任意取值。
我们统计出在有限制的情况下,每个数位出现的次数 c n t [ i ] , 0 ≤ i ≤ 9 cnt[i],0\le i\le9 cnt[i],0i9,假设当前求 [ 0 , r ] [0,r] [0,r]区间,区间众数为 d d d的数的个数( 最终 a n s ( r ) − a n s ( l − 1 ) ans(r)-ans(l-1) ans(r)ans(l1)即可 )。

如果 l i m i t ( 数 位 限 制 ) limit( 数位限制) limit 或者 l e a d ( 有 前 导 0 ) lead(有前导0) lead0,那么就继续 d f s dfs dfs 下去。

如果没有数位限制,且没有前导0,设此时已经记录了 c n t [ i ] , 0 ≤ i ≤ 9 cnt[i],0\le i\le9 cnt[i],0i9,且剩下的可以任意填数位数为 l e n len len

我们枚举 d d d 在剩下的可选任意数的位置上出现的次数,假设这个次数为 c n t d , 0 ≤ c n t d ≤ l e n cntd,0\le cntd \le len cntd,0cntdlen ,那么现在 d d d 这个数位一共出现了 c n t [ d ] + c n t d cnt[d]+cntd cnt[d]+cntd 次。

我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]为除了这一位 d d d 外,考虑了 0 − 9 0-9 09中前 i i i 个数,且一共占据了 j j j 位的方案数。

很明显初始化 d p [ 0 ] [ 0 ] = c [ l e n ] [ c n t d ] dp[0][0]=c[len][cntd] dp[0][0]=c[len][cntd]

d p [ i ] [ j ] dp[i][j] dp[i][j]的转移有, d p [ i ] [ j ] = ∑ k = 0 m i n ( j , c n t [ d ] + c n t d − c n t [ i − 1 ] − 1 ) d p [ i − 1 ] [ j − k ] ∗ c [ l e n − c n t d − ( j − k ) ] [ k ] dp[i][j]=\sum\limits_{k=0}^{min(j,cnt[d]+cntd-cnt[i-1]-1)}dp[i-1][j-k]*c[len-cntd-(j-k)][k] dp[i][j]=k=0min(j,cnt[d]+cntdcnt[i1]1)dp[i1][jk]c[lencntd(jk)][k]

对于 c n t d   ( 0 ≤ c n t d ≤ l e n ) cntd\space (0\le cntd \le len) cntd (0cntdlen),每个 c n t d cntd cntd 跑一遍 d p dp dp a n s + = d p [ 10 ] [ l e n − c n t d ] ans+=dp[10][len-cntd] ans+=dp[10][lencntd]

时间复杂度: O ( 能 过 ) O(能过) O()

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<set>
#include<list>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
//#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;

const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const int mod=1e9+7;
const double eps=1e-1;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=100100;
const int maxp=1100;
const int maxm=4000100;
const int up=1000;

ll cnt[10],dp[11][21],c[21][21],pos[20];
ll l,r,d;

ll dfs(int len,bool limit,bool lead)
{
    if(len==0)
    {
        for(int i=0;i<10;i++)
        {
            if(i==d) continue;
            if(cnt[i]>=cnt[d]) return 0;
        }
        return 1;
    }
    if(!limit&&!lead)
    {
        ll ans=0;
        for(int dd=0;dd<=len;dd++)//枚举剩下的位有多少个d
        {
            memset(dp,0,sizeof(dp));
            dp[0][0]=c[len][dd];
            for(int i=1;i<=10;i++)//考虑0-9的前i位
            {
                if(i-1==d)
                {
                    for(int j=0;j<=len-dd;j++)
                        dp[i][j]=dp[i-1][j];
                    continue;
                }
                for(int j=0;j<=len-dd;j++)
                    for(int k=0;k<=min(j,cnt[d]+dd-cnt[i-1]-1);k++)
                        dp[i][j]+=dp[i-1][j-k]*c[len-dd-(j-k)][k];
            }
            ans+=dp[10][len-dd];

        }
        return ans;
    }
    int up=limit?pos[len]:9;
    ll ans=0;
    for(int i=0;i<=up;i++)
    {
        if(!lead||i) cnt[i]++;
        ans+=dfs(len-1,limit&&i==up,lead&&i==0);
        if(!lead||i) cnt[i]--;
    }
    return ans;
}

ll fi(ll x)
{
    int cnt=0;
    while(x)
    {
        pos[++cnt]=x%10;
        x/=10;
    }
    return dfs(cnt,true,true);
}


int main(void)
{
    c[0][0]=1;
    for(int i=1;i<=20;i++)
    {
        c[i][0]=c[i][i]=1;
        for(int j=1;j<i;j++)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
    }

    int tt;
    scanf("%d",&tt);
    while(tt--)
    {
        scanf("%lld%lld%lld",&l,&r,&d);
        printf("%lld\n",fi(r)-fi(l-1));
    }
    return 0;
}


试一下记忆化。
也没快多少。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<unordered_map>
#include<set>
#include<list>
#define ui unsigned int
#define ll long long
#define llu unsigned ll
#define ld long double
#define pr make_pair
#define pb push_back
#define lc (cnt<<1)
#define rc (cnt<<1|1)
//#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;

const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const int mod=1e9+7;
const double eps=1e-1;
const double pi=acos(-1.0);
const int hp=13331;
const int maxn=100100;
const int maxp=1100;
const int maxm=4000100;
const int up=1000;

ll dp[11][21],c[21][21],pos[20];
ll l,r,d;
vector<int>cnt(10,0);
map<vector<int>,ll>mp[20][2];


ll dfs(int len,bool limit,bool lead)
{
    if(!limit&&mp[len][lead].count(cnt)) return mp[len][lead][cnt];
    if(len==0)
    {
        for(int i=0;i<10;i++)
        {
            if(i==d) continue;
            if(cnt[i]>=cnt[d]) return mp[len][lead][cnt]=0;
        }
        return mp[len][lead][cnt]=1;
    }
    if(!limit&&!lead)
    {
        ll ans=0;
        for(int dd=0;dd<=len;dd++)//枚举剩下的位有多少个d
        {
            memset(dp,0,sizeof(dp));
            dp[0][0]=c[len][dd];
            for(int i=1;i<=10;i++)//考虑0-9的前i位
            {
                if(i-1==d)
                {
                    for(int j=0;j<=len-dd;j++)
                        dp[i][j]=dp[i-1][j];
                    continue;
                }
                for(int j=0;j<=len-dd;j++)
                    for(int k=0;k<=min(j,cnt[d]+dd-cnt[i-1]-1);k++)
                        dp[i][j]+=dp[i-1][j-k]*c[len-dd-(j-k)][k];
            }
            ans+=dp[10][len-dd];

        }
        return mp[len][lead][cnt]=ans;
    }
    int up=limit?pos[len]:9;
    ll ans=0;
    for(int i=0;i<=up;i++)
    {
        if(!lead||i) cnt[i]++;
        ans+=dfs(len-1,limit&&i==up,lead&&i==0);
        if(!lead||i) cnt[i]--;
    }
    if(!limit) mp[len][lead][cnt]=ans;
    return ans;
}

ll fi(ll x)
{
    int cnt=0;
    while(x)
    {
        pos[++cnt]=x%10;
        x/=10;
    }
    return dfs(cnt,true,true);
}


int main(void)
{
    c[0][0]=1;
    for(int i=1;i<=20;i++)
    {
        c[i][0]=c[i][i]=1;
        for(int j=1;j<i;j++)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
    }

    int tt;
    scanf("%d",&tt);
    while(tt--)
    {
        for(int i=0;i<20;i++)
        {
            for(int j=0;j<2;j++)
                mp[i][j].clear();
        }
        scanf("%lld%lld%lld",&l,&r,&d);
        printf("%lld\n",fi(r)-fi(l-1));
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值