pat 甲级 1049(思维 + 数学 | 数位dp)

传送门

参考文章

题目:

计算出从1~n中的所有数字中出现了多少个1.

打表分析:

通过观察0~200的数字可以发现

在0~9的数字中只有一个1,

在10~19的数字中,十位数1产生10个1,个位数产生一个1

在20~99的数字中,每10个个位数产生一个1。

所以我们想要1~n范围内的数字1的个数可逐位分析。

eg:对于数字1023,可以先考虑个位数的1,然后十位数产生的1,然后百,千位数产生的1的个数。

对于个位,3产生的1的个数(在大于个位的数字可以是0~101共102种情况,还有1010~10191种) = (102 + 1) = 103;

对于十位,2产生的1的个数(在大于十位数字的可以是0~09共100种情况,还有0999~1000种) = (10 + 1)*10 = 110;

对于百位,0产生的1的个数(只有1000种) = 1*100 = 100;

对于千位,1产生的1的个数(1自带前面所有的,有23种,还有当前位为0的情况,1种) = 24;

ans = 103 + 110 + 100 + 24 = 337;

所以可以从个位开始从后向前逐位考虑,设当前位的值位tp。

(1)tp == 0的情况,ans += n*d(n表示tp之前的数字,d表示当前的位数);

(2)tp == 1的情况,ans += n*d + pre + 1,(pre表示之前低于tp的位数的数值);

(3)tp > 1的情况,ans += (n+1)*d;

代码:

#include <bits/stdc++.h>
using namespace std;

int main(void)
{
    int n;
    scanf("%d",&n);
    int ans = 0,pre = 0,d = 1,tp;
    while(n)
    {
        tp = n%10;
        n /= 10;
        if(tp == 0) ans += n * d;
        else if(tp == 1) ans += n*d + pre + 1;
        else ans += (n+1)*d;
        pre += tp*d;
        d *= 10;
    }
    printf("%d\n",ans);
    return 0;
}

数位dp的写法:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll dp[22][22],n,m,a[25];
ll dfs(ll pos,bool fg,bool zero,ll sum,ll d)
{
    if(pos == -1) return sum;
    if(fg == false && zero == false && ~dp[pos][sum]) return dp[pos][sum];
    ll ans = 0,len = (fg==true?a[pos]:9);
    bool ff;
    for(ll i=0;i<=len;i++)
    {
        ff = zero && (i==0);//判断前导零
        ans += dfs(pos-1,fg&&(i==len),ff,sum+( (ff == false)&&(i==d) ),d);
    }
    if(fg == false && zero == false) dp[pos][sum] = ans;
    return ans;
}
ll solve(ll x,ll d)
{
    ll pos = 0;
    while(x)
    {
        a[pos++] = x%10;
        x/=10;
    }
    memset(dp,-1,sizeof(dp));
    return dfs(pos-1,true,true,0,d);
}
int main(void)
{
    scanf("%lld",&n);
    printf("%lld\n",solve(n,1));
    return 0;
}
发布了438 篇原创文章 · 获赞 16 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41829060/article/details/103374947