Codeforces - 55D Beautiful numbers (数位dp+数论)

题意:求[L,R](1<=L<=R<=9e18)区间中所有能被自己数位上的非零数整除的数的个数

分析:丛数据量可以分析出是用数位dp求解,区间个数可以转化为sum(R)-sum(L-1)前缀和相减的形式。如果一个数能被所有位上数的最小公倍数(lcm)整除,便是符合要求的数。

但是直接传递一个数n的话,dp数组肯定开不下。那么怎么让其传递的值更小呢。

先了解一个等式:  sum%(x*n)%x == sum%x

可以将一个数枚举到第pos位之前的值视作sum,x是所有位上数的lcm。那么我们可以在dfs递归的时候传递sum%(x*n),这样就能在维护sum%x性质的同时,使sum变得很小。

x*n怎么取呢?1-9的最小公倍数是2520,显然它作为此处的x*n在空间复杂度上是支持的。

此时可以确定dp数组是三维的。dp[i][j][k],其代表枚举到第i位,前面所有位组成的数%2520的余数是j,前面所有位上的lcm是k的数位状态下符合要求的个数。

还有一个问题,20*2520*2520的数组是开不下的。但是第三维有大部分的数都是不会出现的,所以可以对2520的因数离散化,数量是小于50的,空间复杂度是可以承受的。

#include<bits/stdc++.h>
using namespace std;
const int maxn =40;
typedef long long ll;
ll dp[maxn][2520][50];
int a[maxn];
int dic[3000];

ll gcd(ll a,ll b)
{
    if(b==0) return a;
    return gcd(b,a%b);
}

void init()
{
    int MOD=2520,cnt=0;
    for(int i=1;i<=MOD;++i){
        if(MOD%i==0)
            dic[i]=cnt++;
    }
    memset(dp,-1,sizeof(dp));
}

ll dfs(int pos,int mod=0,int lcm = 1,bool limit=true)
{
    if (pos==-1) return mod%lcm==0;
    int id =dic[lcm];
    if(!limit && dp[pos][mod][id]!=-1) return dp[pos][mod][id];
    int up = limit?a[pos]:9;
    ll res=0;
    for(int i=0;i<=up;++i){
        if(i) res+=dfs(pos-1,(mod*10+i)%2520,lcm*i/gcd(lcm,i),limit && i==a[pos]);
        else res+=dfs(pos-1,(mod*10)%2520,lcm,limit && i==a[pos]);
    }
    if(!limit) dp[pos][mod][id] = res;
    return res;
}

ll solve(ll n)
{
    int pos=0;
    while(n){
        a[pos++]=n%10;
        n/=10;
    }
    return dfs(pos-1);
}


int main()
{
    #ifndef ONLINE_JUDGE
         freopen("in.txt","r",stdin);
         freopen("out.txt","w",stdout);
    #endif
    init();
    int T;
    scanf("%d",&T);
    while(T--){
        ll L,R;
        scanf("%lld%lld",&L,&R);
        printf("%lld\n",solve(R)-solve(L-1));
    }
    return 0;
}

 

posted @ 2018-07-31 16:32  xiuwenL  阅读(145)  评论(0编辑  收藏  举报