Dp-digit
数位dp一般就是要统计一个区间[l,r]内满足一些条件数的个数。
题目来源: http://www.51nod.com/Challenge/Problem.html#problemId=1042
感谢优秀博客:https://www.cnblogs.com/y2823774827y/p/10301145.html
以上博客写的很详细,这里稍作总结
算法思想
1.首先计算10^i内1-9出现的次数,下面简称为各数贡献值bj
dp[i] = 10dp[i-1] + 10^(i-1)
其实很好理解,如i=1,显然10以内1-9各数贡献值均为1;i=2,100以内各数贡献值101+101
2
3
4
5
6
7
8
9
10
11typedef long long ll;
ll a[25], digit[25], dp[25], countl[20], countr[20];
void init() //统计10^(i-1)内各数出现次数,dp[i]存储
{ //dp[i] = 10*dp[i-1] + 10^(i-1)
digit[0] = 1;
for(ll i = 1; i <= 20; ++i)
{
dp[i] = (dp[i-1]<<3) + (dp[i-1]<<1) + digit[i-1];
digit[i] = (digit[i-1]<<3) + (digit[i-1]<<1);
}
}2.接着计算数n内各数贡献值
如n=43102,遍历每一位数,遍历顺序从n到1,将其拆为{40000,3000,100,0,2}
对于第i位,记录2项 1)拆分的第i位数贡献值(如i=1,为b[j]+=4*dp[5])
2)第i位数的贡献值(如i=1,为b[j]+=(3102+1))1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void solve(int m, ll *b)
{
ll len = 0, ans = m;
while(m) //字符串存储m
{
a[++len] = m % 10;
m /= 10;
}
for(ll k = len; k>=1; --k)
{
for(int i = 0; i < 10; ++i)
b[i] += dp[k-1] * a[k]; //10^(k-1)内各数的贡献值
for(int i = 0; i < a[k]; ++i)
b[i] += digit[k-1]; //第k位数的贡献值,不包含a[k]
ans -= a[k]*digit[k-1]; //m = m % ( 10^(k-1) )
b[a[k]] += (ans + 1); //a[k]的贡献值
b[0] -= digit[k-1]; //0的贡献值在之前加上的要删去
}
}3.最后可以求[a,b]间各数的贡献值,算两次即可 solve(b) - solve[a-1]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int main()
{
init();
ll m, n;
scanf("%lld%lld", &m, &n);
solve(m-1, countl);
solve(n, countr);
for(int i = 0; i < 10; ++i)
printf("%lld ", countl[i]);
printf("\n");
for(int i = 0; i < 10; ++i)
printf("%lld ", countr[i]);
printf("\n");
for(int i = 0; i < 10; ++i)
printf("%lld ",countr[i] - countl[i]);
return 0;
}
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!