数位dp

数位动态规划

求给定区间内满足某种数位条件(数位之和,数码个数等)的数量,核心思想为“逐位确定”,复杂度log2n

「一本通 5.3 例 1」Amount of Degrees

求给定区间[X,Y]中满足下列条件的整数个数:这个数恰好等于K 个互不相等的B的整数次幂之和。
17 = 24+ 20,
18 = 24+ 21,
20 = 24+ 22.

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int f[35][35];//int型存储
int x, y, b;
ll pos[35];
int len;
void init()//预处理,结果为杨辉三角
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1; i<=33; i++)
    {
        f[i][0]=1;
        for(int j=1; j<=i; j++)
        {
            f[i][j]=f[i-1][j-1]+f[i-1][j];//状态转移方程
        }
    }
}
ll solve(int t, int k)
{
    len=0;
    while(t)//按进制存储
    {
        pos[++len]=t%b;
        t/=b;
    }
    ll ans=0;
    for(int i=len;i>=1;i--)
    {
        if(pos[i]>1)//不允许系数出现,在此计算后转出
        {
            ans+=f[i-1][k]+f[i-1][k-1];
            break;
        }
        else if(pos[i]==1)//左子树加入,进入右子树
        {
            ans+=f[i-1][k];
            k--;
        }
        if(k<0)
            break;
    }
    return ans;
}
int main()
{
    init();
    int k;
    while(~scanf("%d%d%d%d", &x, &y, &k, &b))
    {
        ll ans1 = solve(y+1, k);
        ll ans2 = solve(x, k);
        printf("%lld\n", ans1-ans2);//1到y减去1到x
    }
    return 0;
}

「一本通 5.3 例 2」数字游戏

题目描述
科协里最近很流行数字游戏。某人命名了一种不降数,这种数字必须满足从左到右各位数字成小于等于的关系,如 123,446。现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
输入格式
有多组测试数据。每组只含两个数字 a,b,意义如题目描述。(a,b<231-1)
输出格式
每行给出一个测试数据的答案,即 [a,b]之间有多少不降数。
样例输入
1 9
1 19
样例输出
9
18

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int f[11][11];
int d[11];
int a,b;
int dp(int v,int u,int flag)
//v代表当前位,u代表当前状态,flag判断是否越界
//从高位向低位搜,v递减
{
    if(v==0) return 1;//枚举完了
    if(!flag && f[v][u]!=-1)//优化步,如果不越界且这步已经搜完,直接返回搜的结果
    {
        return f[v][u];
    }

    int ans=0;
    int n;//结束标志
    if(flag)
        n=d[v];//如果当前枚举的数越界,也就是擦边,那么枚举到最大
    else
        n=9;//如果当前不擦边,就随便枚举

    for(int i=u; i<=n; i++)
    {
        if(flag&&i==n)
            ans+=dp(v-1,i,1);
        else
            ans+=dp(v-1,i,0);
    }
    if(!flag) f[v][u]=ans;//如果未越界,存入该位搜到u的结果
    return ans;
}
ll solve(int num)
{
    memset(f,-1,sizeof(f));
    int cnt=0;
    while(num)
    {
        d[++cnt]=num%10;//倒序存入数据各位
        num/=10;
    }
    return dp(cnt,0,1);//cnt为位数
}
int main()
{
    while(~scanf("%d%d",&a,&b))
    {
        printf("%lld\n",solve(b)-solve(a-1));
    }
    return 0;
}


洛谷P2657 [SCOI2009]windy数

洛谷题解,详细明了
windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,在A和B之间,包括A和B,总共有多少个windy数?
输入格式
包含两个整数,A B。
输出格式
一个整数
输入输出样例
输入 #1
1 10
输出 #1
9
输入 #2
25 50
输出 #2
20
说明/提示
100%的数据,满足 1 <= A <= B <= 2000000000 。

——————————————————————————————————————————
本题预处理为dp[i][j]=Σdp[i-1][k],然后进行3部分操作
1.求i-1位所有windy数
2.求i位但首位小于所求数首位的windy数
3.首位为所求数首位,逐位求比所求数该位小的windy数再相加,即 ∑ i = 1 位 数 − 1 \sum_{i=1}^{位数-1} i=11dp[i][j]

#include<bits/stdc++.h>
using namespace std;
//设dp[i][j]为长度为i中最高位是j的windy数的个数
int p,q,dp[11][11],a[11];
void init()//预处理
{
    for(int i=0;i<=9;i++)   
        dp[1][i]=1; //0,1,2,3,4...9都属于windy数 
    for(int i=2;i<=10;i++)
    {
        for(int j=0;j<=9;j++)
        {
            for(int k=0;k<=9;k++)
            {
                if(abs(j-k)>=2) 
                    dp[i][j]+=dp[i-1][k]; 
            }
        }
    }//从第二位开始 每次枚举最高位j 并找到k 使得j-k>=2 
}
int solve(int x) 
{
    memset(a,0,sizeof(a));
    int len=0,ans=0;
    while(x)
    {
        a[++len]=x%10;
        x/=10;
    }
    //分为几个板块 先求len-1位的windy数 必定包含在区间里的 
    for(int i=1;i<=len-1;i++)
    {
        for(int j=1;j<=9;j++)
        {
            ans+=dp[i][j];
        } 
    }
    //然后是len位 但最高位<a[len]的windy数 也包含在区间里 
    for(int i=1;i<a[len];i++)
    {
        ans+=dp[len][i];
    } 
    //接着是len位 最高位与原数相同的 最难搞的一部分 
    for(int i=len-1;i>=1;i--)
    {
        //i从最高位后开始枚举 
        for(int j=0;j<=a[i]-1;j++)//j要在a[i]之下
           {
                if(abs(j-a[i+1])>=2)    ans+=dp[i][j]; //判断和上一位(i+1)相差2以上
                   //如果是 ans就累加 
           } 
        if(abs(a[i+1]-a[i])<2)       break;
      //  if(i==1)   ans+=1;
    }
    return ans;
}
int main()
{
    init();
    scanf("%d%d",&x,&y);
    printf("%d\n",solve(y+1)-solve(x));
    return 0;
}
}

CF1036C Classy Numbers(类似数字游戏)

定义一个数字是“好数”,当且仅当它的十进制表示下有不超过3个数字1∼9
举个例子:4,200000,10203是“好数”,然而4231,102306,7277420000不是
给定[l,r],问有多少个x使得l≤x≤r,且x是“好数”
一共有T(1≤T≤104 )组数据,对于每次的询问,输出一行一个整数表示答案
1≤li​≤ri​≤1018
输入
4
1 1000
1024 1024
65536 65536
999999 1000001
输出
1000
1
0
2

#include <bits/stdc++.h>
#define ll long long
using namespace std;
ll f[20][5];
ll d[20];
ll a,b;
int poi;
ll dp(int v,int u,bool flag)
//v代表当前位,u代表非0的个数,flag判断是否越界
//从高位向低位搜,v递减
{
    if(v==0) return 1;//枚举完了
    if(!flag && f[v][u]!=-1)//优化步,如果不越界且这步已经搜完,直接返回搜的结果
    {
        return f[v][u];
    }

    ll ans=0;
    int n;//结束标志
    if(flag)
        n=d[v];//如果当前枚举的数越界,也就是擦边,那么枚举到最大
    else
        n=9;//如果当前不擦边,就随便枚举
    for(int i=0; i<=n; i++)
    {
        if(i==0)
        {
            if(flag&&i==n)
                ans+=dp(v-1,u,1);
            else
                ans+=dp(v-1,u,0);
        }
        else if(u<3)//i!=0&&u>=3即跳出
        {
            if(flag&&i==n)
                ans+=dp(v-1,u+1,1);
            else
                ans+=dp(v-1,u+1,0);
        }

    }
    if(!flag) f[v][u]=ans;//如果未越界,存入该位搜到u的结果
    return ans;
}
ll solve(ll num)
{
    memset(f,-1,sizeof(f));
    int cnt=0;
    while(num)
    {
        d[++cnt]=num%10;//倒序存入数据各位
        num/=10;
    }
    return dp(cnt,0,1);//cnt为位数
}
int main()
{
    scanf("%d",&poi);
    while(poi--)
    {
        scanf("%lld%lld",&a,&b);
        printf("%lld\n",solve(b)-solve(a-1));
    }
    return 0;
}



CF1073E Segment Sum

题目描述
You are given two integers l and r ( l≤r ). Your task is to calculate the sum of numbers from l to r (including l and r ) such that each number contains at most k different digits, and print this sum modulo 998244353 .
For example, if k = 1 then you have to calculate all numbers from l to r such that each number is formed using only one digit. For l=10,r=50 the answer is 11 + 22 + 33 + 44 = 110.
输入格式
The only line of the input contains three integers l , r and k ( 1≤l≤r<1018,1≤k≤10 ) — the borders of the segment and the maximum number of different digits.
输出格式
Print one integer — the sum of numbers from l to r such that each number contains at most k different digits, modulo 998244353 .

题意翻译
给定K,L,R,求L~R之间最多不包含超过K个数码的数的和。
K<=10,L,R<=1e18
输入输出样例
输入 #1
10 50 2
输出 #1
1230
输入 #2
1 2345 10
输出 #2
2750685
输入 #3
101 154 2
输出 #3
2189
说明/提示
For the third example the answer is 101 + 110 + 111 + 112 + 113 + 114 + 115 + 116 + 117 + 118 + 119 + 121 + 122 + 131 + 133 + 141 + 144 + 151 = 2189


此题需要用到状压dp,此为90分,之后来补100分

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=998244353;
int d[20];
ll a,b;
int k;
ll xx;
void dp(int v,int u,int u0,int u1,int u2,int u3,int u4,int u5,int u6,int u7,int u8,int u9,bool flag,ll ans)
{
    ans%=mod;
    if(v==0)
    {
        xx=(xx+ans)%mod;
        //printf("%lld\n",ans);
        return ;
    }
    int n;
    if(flag)
        n=d[v];
    else
        n=9;
    for(int i=0; i<=n; i++)
    {
        if((i==0&&u0==1)||(i==1&&u1==1)||(i==2&&u2==1)||(i==3&&u3==1)||(i==4&&u4==1)||(i==5&&u5==1)||(i==6&&u6==1)||(i==7&&u7==1)||(i==8&&u8==1)||(i==9&&u9==1))
        {
            if(flag&&i==n)
                dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
            else
                dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
        }
        else if(ans==0&&i==0)
        {
            if(flag&&i==n)
                dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
            else
                dp(v-1,u,u0,u1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
        }
        else if(u<k)
        {
            if(flag&&i==n)
            {
                if(i==0) dp(v-1,u+1,1,u1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
                else if(i==1) dp(v-1,u+1,u0,1,u2,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
                else if(i==2) dp(v-1,u+1,u0,u1,1,u3,u4,u5,u6,u7,u8,u9,1,ans*10+i);
                else if(i==3) dp(v-1,u+1,u0,u1,u2,1,u4,u5,u6,u7,u8,u9,1,ans*10+i);
                else if(i==4) dp(v-1,u+1,u0,u1,u2,u3,1,u5,u6,u7,u8,u9,1,ans*10+i);
                else if(i==5) dp(v-1,u+1,u0,u1,u2,u3,u4,1,u6,u7,u8,u9,1,ans*10+i);
                else if(i==6) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,1,u7,u8,u9,1,ans*10+i);
                else if(i==7) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,1,u8,u9,1,ans*10+i);
                else if(i==8) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,1,u9,1,ans*10+i);
                else if(i==9) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,u8,1,1,ans*10+i);;
            }
            else
            {
                if(i==0) dp(v-1,u+1,1,u1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
                else if(i==1) dp(v-1,u+1,u0,1,u2,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
                else if(i==2) dp(v-1,u+1,u0,u1,1,u3,u4,u5,u6,u7,u8,u9,0,ans*10+i);
                else if(i==3) dp(v-1,u+1,u0,u1,u2,1,u4,u5,u6,u7,u8,u9,0,ans*10+i);
                else if(i==4) dp(v-1,u+1,u0,u1,u2,u3,1,u5,u6,u7,u8,u9,0,ans*10+i);
                else if(i==5) dp(v-1,u+1,u0,u1,u2,u3,u4,1,u6,u7,u8,u9,0,ans*10+i);
                else if(i==6) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,1,u7,u8,u9,0,ans*10+i);
                else if(i==7) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,1,u8,u9,0,ans*10+i);
                else if(i==8) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,1,u9,0,ans*10+i);
                else if(i==9) dp(v-1,u+1,u0,u1,u2,u3,u4,u5,u6,u7,u8,1,0,ans*10+i);
            }
        }
    }
}
ll solve(ll num)
{
    xx=0;
    int cnt=0;
    while(num)
    {
        d[++cnt]=num%10;//倒序存入数据各位
        num/=10;
    }
    dp(cnt,0,0,0,0,0,0,0,0,0,0,0,1,0);
    return xx;//cnt为位数
}
int main()
{
    scanf("%lld%lld%lld",&a,&b,&k);
    if(k==0)
    {
        printf("0\n");
        return 0;
    }
    else if(k==10)
    {
        ll ans1=(a+b)*(b-a+1)/2;
        printf("%lld\n",ans1);
        return 0;
    }
    ll ans1=solve(b)-solve(a-1);
    ans1=(ans1%mod+mod)%mod;
    printf("%lld\n",ans1);
    return 0;
}



P4317 花神的数论题(类似Amount of Degrees)

题目描述
设 sum(i) 表示 i 的二进制表示中 1 的个数。给出一个正整数 N ,要问你 ∏ i = 1 n \prod_{i=1}^{n} i=1n sum(i) ,也就是sum(1)∼sum(N) 的乘积。
输入格式
一个正整数 N。
输出格式
一个数,答案模 10000007 的值。
输入输出样例
输入
3
输出
2
说明/提示
对于 100% 的数据,N≤10^15

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod= 10000007;
ll f[150][150];//int型存储
ll n;
ll pos[70];
int len;
ll quickmod(ll a,ll b)//快速幂
{
    ll r=1;
    while(b)
    {
        if(b&1)
            r=r*a%mod;
        a=a*a%mod;
        b/=2;
    }
    return r%mod;
}
void init()//预处理,结果为杨辉三角
{
    memset(f,0,sizeof(f));
    f[0][0]=1;
    for(int i=1; i<=60; i++)//开多了会爆long long 具体开到多少我也不知道
    {
        f[i][0]=1;
        for(int j=1; j<=i; j++)
        {
            f[i][j]=f[i-1][j-1]+f[i-1][j];//状态转移方程
        }
    }
}
int weishu(ll t)
{
    len=0;
    while(t)//按进制存储
    {
        pos[++len]=t%2;
        t=t/2;
    }
    return len;
}
ll solve(int k)
{
    ll ans=0;
    for(int i=len;i>=1;i--)
    {
        if(pos[i]==1)//左子树加入,进入右子树
        {
            ans=ans+f[i-1][k];
            k--;
        }
        if(k<0)
            break;
    }
    return ans;
}
int main()
{
    init();
    int k;
    while(~scanf("%lld",&n))
    {
        weishu(n+1);
        ll ans=1;
        for(int k=1;k<=len;k++)
        {
            ll ans1 = solve(k);
            ans=(ans*quickmod(k,ans1))%mod;//因为ans1要做幂数,此前操作不可取模
        }
        printf("%lld\n",ans);
    }
    return 0;
}


P4999 烦人的数学作业

给出一个区间L~R,求L到R区间内每个数的数字和,如123这个数的数字和为1+2+3=6。
同学们纷纷做出来了,Mr.G一看这最后一题跟摆设没区别了呀,于是他迅速修改了题目,把范围定得非常非常大,且有T组数据,将最终的答案mod 109+7。
(1≤L≤R≤1018 ) (1≤T≤20)
同学们纷纷被难住了。但H为了备战NOIP2018,没有时间完成Mr.G的数学作业~~(其实是不想做QwQ)~~,所以Ta找到了你,希望你帮助Ta和同学完成这烦人的数学作业!
输入格式
输入共T+1行,
第1行读入T。代表有T组数据;
第2~T+1行。读L i和R i
输出格式
输出共T行,
每行输出L i和R i的区间数字和mod 109+7
输入 #1
2
24 69
70 120
输出 #1
411
498
说明/提示
对于 50% 的数据,1≤L≤R≤108
对于 100% 的数据,1≤L≤R≤1018
1≤T≤20。


此题与数字计数可用同种方法解决,此处给出数位dp做法

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod= 1000000007;
ll l,r,f[20][20],ans[10];
int d[20],len,t;
ll dp(int k,int v,int u,bool flag)
{
    if(v==0)
        return u;
    if(!flag&&f[v][u]!=-1)
        return f[v][u];
    int n=flag ? d[v]:9;
    ll sum=0;
    for(int i=0; i<=n; ++i)
    {
        sum+=dp(k,v-1,u+(i==k),flag&&(i==n));
    }
    if(!flag)
        f[v][u]=sum;
    return sum;
}
void change(ll num)
{
    len=0;
    while(num)
    {
        d[++len]=num%10;
        num/=10;
    }
}
void solvel(ll num)
{
    change(num);
    for(int i=1; i<=9; i++)
    {
        memset(f,-1,sizeof(f));
        ans[i]=(ans[i]-dp(i,len,0,1)+mod)%mod;
    }
}
void solver(ll num)
{
    change(num);
    for(int i=1; i<=9; i++)
    {
        memset(f,-1,sizeof(f));
        ans[i]=dp(i,len,0,1)%mod;
    }
}
int main()
{
    scanf("%lld",&t);
    while(t--)
    {
        ll ansl=0;
        scanf("%lld%lld",&l,&r);
        solver(r);
        solvel(l-1);//此两处都计算了0,相减即相消
        for(int i=1; i<=9; i++)
            ansl=(ansl+i*(ans[i]+mod)%mod+mod)%mod;
        printf("%lld\n",ansl);
    }
    return 0;
}

P4124 [CQOI2016]手机号码

题目描述
人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量。
工具需要检测的号码特征有两个:号码中要出现至少 3 个相邻的相同数字;号码中不能同时出现 8 和 4。号码必须同时包含两个特征才满足条件。满足条件的号码例如:13000988721、23333333333、14444101000。而不满足条件的号码例如:1015400080、10010012022。
手机号码一定是 11 位数,前不含前导的 0。工具接收两个数 L 和 R,自动统计出 [L,R] 区间内所有满足条件的号码数量。L 和 R 也是 11 位的手机号码。
输入格式
输入文件内容只有一行,为空格分隔的 22个正整数 L,R。
输出格式
输出文件内容只有一行,为 1 个整数,表示满足条件的手机号数量。
输入输出样例
输入 #1
12121284000 12121285550
输出 #1
5
说明/提示
样例解释:满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550。
数据范围:1010 ≤L≤R<1011


看到这篇题解才终于有种了解数位dp的感觉,还是太菜了,枯了。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod= 1000000007;
ll l,r,f[20][20][20][2][2][2][2];
int d[20],len,t;
ll dp(int v,int a,int b,bool c,bool flag,bool p4,bool p8)
{
    if(p4&&p8)
    {
        return 0;
    }
    if(v<=0) return c;
    if(f[v][a][b][c][flag][p4][p8]!=-1)
        return f[v][a][b][c][flag][p4][p8];
    int n=flag ? d[v]:9;
    ll sum=0;
    for(int i=0; i<=n; i++)
    {
        sum+=dp(v-1,i,a,c||(i==b&&i==a),flag&&(i==n),p4||(i==4),p8||(i==8));
    }
    f[v][a][b][c][flag][p4][p8]=sum;
    return sum;
}
void change(ll num)
{
    len=0;
    while(num)
    {
        d[++len]=num%10;
        num/=10;
    }
}
ll solve(ll num)
{
    //if(num<1e10) return 0;
    memset(f,-1,sizeof(f));
    change(num);
    ll ans=0;
    for(int i=1;i<=d[11];i++)
    {
        ans+=dp(10,i,0,0,i==d[11],i==4,i==8);
    }
    return ans;
}
int main()
{
    scanf("%lld%lld",&l,&r);
    printf("%lld\n",solve(r)-solve(l-1));
    return 0;
}

P4127 [AHOI2009]同类分布

题目描述
给出两个数a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
输入格式
一行,两个整数a和b
输出格式
一个整数,表示答案
输入输出样例
输入 #1
10 19
输出 #1
3
说明/提示
对于所有的数据,1 ≤ a ≤ b ≤ 1018


万万没想到还有枚举sum和做模数的操作,学到了学到了
ps.卡常技巧:首先,我们可以剪枝,若sum(数字各位之和>mod),直接return 0,因为我们越往后,sum只会越来越大,不可能等于mod。
然后就是清空DP数组时将memset改为for循环,我们只用将我们上一次用到的数组清空(1~枚举的mod)就可以了(实测跑的飞快)
再然后就是搜索,正常情况下我们是这样写的:
cout<<calc(b)-calc(a-1);
但是我们忽视了一个问题,那就是相同的mod我们应该只记搜一次,但因为我们分开来计算,所以就算了两次,常数巨大>_<
那么我们就在计算b的答案的同时顺便减去a的答案就行了,这样我们每个mod只用到了一遍。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll l,r,f[20][2][200][200];
int d[20],len,t;
int mod;
int suml;
ll dp(int v,bool flag,int sum,int num)
{
    if(v==0)
    {
        if(num==0&&sum==mod)
            return 1;
        else
            return 0;
    }
    if(f[v][flag][sum][num]!=-1)
        return f[v][flag][sum][num];
    int n=flag ? d[v]:9;
    ll ans=0;
    //printf("1#   %d %d %d\n",v,d[v],n);
    for(int i=0; i<=n; i++)
    {
        if(sum+i!=0)
        {
            //printf("2#   %d %lld %lld\n",v,sum+i,num*10+i);
            ans+=dp(v-1,flag&&(i==n),sum+i,(num*10+i)%mod);
        }
        else
        {
            //printf("3#   %d %lld %lld\n",v,sum+i,num*10+i);
            ans+=dp(v-1,flag&&(i==n),sum+i,(num*10+i)%mod);
        }
    }
    f[v][flag][sum][num]=ans;
    return ans;
}
void change(ll num)
{
    len=0;
    suml=0;
    while(num)
    {
        d[++len]=num%10;
        suml+=num%10;
        num/=10;
    }
}
ll solve(ll num)
{
    change(num);
    ll ans=0;
    for(mod=1;mod<=9*len;mod++)
    {
        memset(f,-1,sizeof(f));
        ans+=dp(len,1,0,0);
    }
    return ans;
}
int main()
{
    while(~scanf("%lld%lld",&l,&r))
    {
        printf("%lld\n",solve(r)-solve(l-1));
    }
    return 0;
}

CF55D Beautiful numbers

题目描述
Volodya是一个很皮的男♂孩。他认为一个能被它自己的每一位数上的数整除的数是很妙的。我们先忽略他的想法的正确性(如需证明请百度“神奇海螺”),只回答在l到r之间有多少个很妙的数字。
输入:
总共有t个询问:
第一行:t;
接下来t行:每行两个数l和r。
注意:请勿使用%lld读写长整型(虽然我也不知道为什么),请优先使用cin(或者是%I64d)。
输出:
t行,每行为一个询问的答案。
输入 #1
1
1 9
输出 #1
9
输入 #2
1
12 15
输出 #2
2
提示
1<=t<=10,
1<=li<=ri<=9⋅1018


此题做法还是常规做法,就是时间卡的我脑壳痛。。
需要注意的是,每次搜不需要将dp数组清空,每次都是相同的值,注意这个问题后跑的飞快

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=2520;
ll l,r,f[20][2550][50];
int a[2550];
int d[20],len;
int gcd(int x,int y)
{
    return y ? gcd(y,x%y):x;
}
int lcm(int x,int y)
{
    if(!y)return x;
    return x/gcd(x,y)*y;
}
ll dp(int v,int num,int lc,bool flag)
{
    if(v==0) return num%lc==0;
    if(!flag&&f[v][num][a[lc]]!=-1)
        return f[v][num][a[lc]];
    int n=flag ? d[v]:9;
    ll ans=0;
    for(int i=0; i<=n; i++)
    {
        ans+=dp(v-1,(num*10+i)%mod,lcm(lc,i),flag&&(i==n));
    }
    if(!flag)
        f[v][num][a[lc]]=ans;
    return ans;
}
void change(ll num)
{
    len=0;
    while(num)
    {
        d[++len]=num%10;
        num/=10;
    }
}
ll solve(ll num)
{
    change(num);
    return dp(len,0,1,1);
}
int main()
{
    ios::sync_with_stdio(false);
    int cnt=0;
    for(int i=1;i<=mod;i++)
    {
        if(mod%i==0)
            a[i]=++cnt;
    }
    memset(f,-1,sizeof(f));
    int poi;
    cin>>poi;
    while(poi--)
    {
        cin>>l>>r;
        cout<<(solve(r)-solve(l-1))<<endl;
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值