数位DP

基本思想在于将一个数拆分成每一位的数字,将总问题转化为子问题。

例:求[a,b]中各个数字出现次数。 (luogu2602 数字计数)

1.发现在i位数中(不考虑前导零),每一个数字出现的次数都相同。f[i]=f[i-1]*10+10^(i-1);

2.在ABCD中,将其拆为:A000+B00+C0+D; A000中,考虑后三位每个数字出现次数都为:A*f[3];

3.再加上第一位上小于A(从零开始,算前导零的时候会减去)的数出现了10^3次

4.然而,后面的000~BCD中,每一个数的产生也伴随着A的一次出现,所以A出现次数单独加上BCD*1;

5.处理前导零; 仅在开始填0~9999(len-1个)时会产生。 零的数量单独减去10^(len-1)+10^(len-2)+...+10+1;

不用写两个query, 对于数组,传一个*就好。 详见代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=20;
ll a,b;
ll cnta[20],cntb[20];
ll f[20],ten[20];
void query(ll x,ll *cnt)
{
    int len=0;
    int num[20];
    ll k=x;
    while(k)
    {
        num[++len]=k%10;
        k/=10;  
    }
    for(int i=len;i>=1;i--)
    {
        for(int j=0;j<=9;j++)
         cnt[j]=cnt[j]+f[i-1]*num[i];
        for(int j=0;j<num[i];j++)
         cnt[j]+=ten[i-1];
        ll num2=0;
        for(int j=i-1;j>=1;j--)
        {
            num2=num2*10+num[j];    
        }
        num2++;
        cnt[num[i]]+=num2;
        cnt[0]-=ten[i-1];
    }
}
int main()
{
    scanf("%lld%lld",&a,&b);
    ten[0]=1;
    for(int i=1;i<=15;i++)
    {
        f[i]=f[i-1]*10+ten[i-1];
        ten[i]=ten[i-1]*10; 
    }
    query(a-1,cnta);
    query(b,cntb);
    for(int i=0;i<=9;i++)
     printf("%lld ",cntb[i]-cnta[i]);
    return 0;
}

luogu(P2602)

原文地址:https://www.cnblogs.com/Miracevin/p/9031693.html