P2602 [ZJOI2010] 数字计数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
AC代码(递推实现):
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 15;
ll cnta[N], cntb[N];//数字0到9出现的次数
int nums[N];//用数组存一个数的每个位
ll f[N];//f[i]表示长度为i每个数字出现的次数,每个数字出现的次数是相等的
ll ten[N];//10的i次方
void solve(ll x, ll* cnt)
{
int len = 0;
while (x)
{
nums[++len] = x % 10;
x /= 10;
}
for (int i = len; i >= 1; i--)
{
for (int j = 0; j <= 9; j++)cnt[j]+= f[i - 1] * nums[i];//先算出当前位的后面所有数字出现的次数
for (int j = 0; j < nums[i]; j++)cnt[j] += ten[i - 1];//算出小于当前位的数字出现的次数
ll num = 0;
for (int j = i - 1; j >= 1; j--)//计算当前位后面的数字的大小
num = num * 10 + nums[j];
cnt[nums[i]] += num + 1;
cnt[0] -= ten[i - 1];
}
}
int main()
{
ll a, b; cin >> a >> b;
ten[0]=1;
for (int i = 1; i <= N; i++)
{
f[i] = i * pow(10, i) / 10;
ten[i] = pow(10, i);
}
solve(a - 1, cnta);
solve(b, cntb);
for (int i = 0; i <= 9; i++)
{
cout << cntb[i]-cnta[i]<<" ";
}
return 0;
}
相关解释:
这里从大到小枚举位数,可以将O(n*n)的复杂度降为个位数乘以个位数的级别,非常优秀。从大到小枚举位数,假设这里枚举到了abcdefg七位数,先计算后面六位数每个数字出现的次数,然后计算0到a-1的数字的在第七位次数,最后计算后面六位数的大小,再加个一就是当前位的数字的次数。因为当前位置是0的数字我们计算了一下,也就是前导0,0bcdefg这个数字不符合要求,所以应该剪掉。最后利用前缀和的思想输出最终的答案。
AC代码(记忆化搜索实现):
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 20;
int now,nums[N];
ll f[N][N];//f[i][j]表示后面i位,前面now的个数为j的now的总个数,now是要计算的0到9的任意一位
ll dfs(int pos,int sum,bool lead,bool limit)
{
if(pos==0)return sum;
ll res=0;
if(!limit&&!lead&&f[pos][sum]!=-1)return f[pos][sum];
int up=(limit?nums[pos]:9);
for(int i=0;i<=up;i++)
{
if(i==0&&lead==true)
res+=dfs(pos-1,sum,true,limit&&i==up);
else if(i==now)
res+=dfs(pos-1,sum+1,false,limit&&i==up);
else
res+=dfs(pos-1,sum,false,limit&&i==up);
}
if(!limit&&!lead)f[pos][sum]=res;
return res;
}
ll solve(ll x)
{
memset(f,-1,sizeof f);
int len=0;
while(x)
{
nums[++len]=x%10;
x/=10;
}
return dfs(len,0,true,true);
}
int main()
{
ll a,b;
cin>>a>>b;
for(int i=0;i<=9;i++)
{
now=i;
cout<<solve(b)-solve(a-1)<<" ";
}
return 0;
}
相关解释:
这里的记忆化搜索代码可以当作模板使用来解决计数DP类型的题目。