数位DP

枫林晚

2018-03-12 20:13:11

Personal

2018年3月12日20:00:55 基本思想在于将一个数拆分成每一位的数字,将总问题转化为子问题。 例:求[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, 对于数组,传一个*就好。 详见代码: ```cpp #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)