1833: [ZJOI2010]count 数字计数
Time Limit: 3 Sec Memory Limit: 64 MB
Description
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次。
Input
输入文件中仅包含一行两个整数a、b,含义如上所述。
Output
输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次。
Sample Input
Sample Output
9 20 20 20 20 20 20 20 20 20
HINT
30%的数据中,a<=b<=10^6;
100%的数据中,a<=b<=10^12。
100%的数据中,a<=b<=10^12。
【题目链接】 link
【题意】
RT
【思路】
用dp[i][j][k]表示长度为i,最高位为j的所有数中数码k出现的次数。
为了预处理出dp数组,我们现需要找到转移方程
f[i][j][k]=∑f[i-1][l][k] (j!=k)
f[i][j][k]=∑f[i-1][l][k]+10i-1 (j!=k)
第一种情况就是直接在前面加一个非k的数,结果跟长度为i-1时相同
第二种情况就是在前面加一个k,因为后面i-1位共有2i-1个数,那么就相当于多了2i-1个k
然后就是对读入的数分别求出每个数码有几个,相减即可。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
#define mst(a,b) memset((a),(b),sizeof(a))
#define rush() int T;scanf("%d",&T);while(T--)
typedef long long ll;
const int maxn = 15;
const ll mod = 1e9+7;
const ll INF = 1e18;
const double eps = 1e-6;
ll fac[15];
ll dp[15][10][10];
int digit[15];
ll ans1[10],ans2[10];
void init()
{
mst(dp,0);
fac[0]=1;
for(int i=1;i<=14;i++) fac[i]=fac[i-1]*10;
for(int i=0;i<=9;i++) dp[1][i][i]=1;
for(int i=2;i<=13;i++)
for(int j=0;j<=9;j++)
{
for(int l=0;l<=9;l++)
{
for(int k=0;k<=9;k++)
{
dp[i][j][k]+=dp[i-1][l][k];
}
}
dp[i][j][j]+=fac[i-1];
}
}
void solve(ll x,ll *num)
{
mst(digit,0);
if(x==0) return;
int len=0;
ll tmp=x;
while(tmp)
{
digit[++len]=tmp%10;
tmp/=10;
}
for(int i=1;i<len;i++) //长度小于len的所有情况
for(int j=1;j<=9;j++)
for(int k=0;k<=9;k++)
{
num[k]+=dp[i][j][k];
}
for(int i=1;i<=digit[len]-1;i++) //长度等于len且最高位不超过digit[len]的所有情况
for(int k=0;k<=9;k++)
{
num[k]+=dp[len][i][k];
}
x%=fac[len-1];
num[digit[len]]+=x+1; //例如x=4532,把x变为532,统计532,数码4的个数加上532+1(4000~4532)
for(int i=len-1;i>0;i--) //以此类推
{
for(int j=0;j<digit[i];j++)
for(int k=0;k<=9;k++)
{
num[k]+=dp[i][j][k];
}
x%=fac[i-1];
num[digit[i]]+=(x+1);
}
}
int main()
{
init();
ll x,y;
scanf("%lld%lld",&x,&y);
solve(y,ans1);
solve(x-1,ans2);
for(int i=0;i<=8;i++) printf("%lld ",ans1[i]-ans2[i]);
printf("%lld\n",ans1[9]-ans2[9]);
}