题目:
计算出从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;
}