[NOIP模拟][数位DP]魔法数字

博客探讨了NOIP2016模拟题中涉及的魔法数字问题,通过数位动态规划(DP)解决模LCM的余数问题。文章详细分析了如何利用数位DP优化求解过程,减少了时间复杂度,并指出了解题的关键优化步骤。作者还分享了自己的解题思路和代码注释。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ASDFZ-NOIP2016模拟
题目
这里写图片描述
分析
题解分析:
设p = LCM(q1, q2, q3 … qn),则任意数 s = t * p + r (r < p),很容易推导出:s % qi = r % qi,也就是说只要我们知道一个数模 p 的余数,就能得到它模q1, q2, q3 … qn 的余数。 对于本题而言,p = LCM(1, 2, 3,4, 5, 6, 7, 8, 9) = 2520。我们先把[L, R]的问题转化为[0, R]的问题与[0, L - 1]问题的差值。而[0, x]的问题容易用数位DP解决,我们只需从高位到低位,状态记录当前在哪一位、前面的位数是否与 x相等、9 个数分别是否出现、模 p 的值即可。时间复杂度 O(10 * 2^9 * logR / log10 * 2520)。 (不了解该性质也可以直接应用最小表示法解决) 一个可行的优化:当我们知道尚未加入最后一位前的数模1、 2、3、4、6、7、9 的值时,是否为5 或8的倍数只和最后一位有关,如此可以把状态中的2520 压到252。
自己的分析:
这道题的暴力还是很好拿的。正解的话,首先那个所谓的可行优化貌似是必须的,因为不这样过不了所有点。正解的记忆化搜索让它的复杂度大大下降。而且个人认为8应该是后三位有关系,因为2520>1000,所以不影响。详解见代码注释。

附代码

#include<iostream>
#include<cstring>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<queue>
#include<iomanip>
#include<set>
#include<cctype>
#include<ctime>
#include<algorithm>
using  namespace std;

int k;
long long l,r,ans,bit[20],dp[20][512][252][2];
int len,mark[20];

inline long long readlong()
{
    char ch;long long i=0;int f=1;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        ch=getchar();
        f=-1;
    }
    for(;ch>='0'&&ch<='9';ch=getchar())
        i=(i<<3)+(i<<1)+ch-'0';
    return i*f;
}

inline void solve(long long x)//计算出这个数有多少位,分别每位是什么 
{
    memset(mark,0,sizeof(mark));
    memset(dp,-1,sizeof(dp));
    for(int i=1;i<20;i++)
        if(x<bit[i])
        {
            len=i;
            break;
        }
    for(int i=len;i>=1;i--)
    {
        mark[i]=x%10;
        x=x/10;
    }
}

inline long long dfs(int pos,int vis,int mod,bool limit)//pos是此时的长度,mod就是当前数除以252的余数
{                                                       // limit判断是否达到了最高位 
    if(pos>len)//此时实际上是位数刚好相等,判断有无1~9数字,并且可以整除 
    {
        int cnt=0;
        if(vis&1) cnt++;
        if(vis&(1<<1)&&mod%2==0) cnt++; 
        if(vis&(1<<2)&&mod%3==0) cnt++;
        if(vis&(1<<3)&&mod%4==0) cnt++;
        if(vis&(1<<4)&&mod%5==0) cnt++;
        if(vis&(1<<5)&&mod%6==0) cnt++;
        if(vis&(1<<6)&&mod%7==0) cnt++;
        if(vis&(1<<7)&&mod%8==0) cnt++;
        if(vis&(1<<8)&&mod%9==0) cnt++;
        if(cnt>=k) return 1; 
        else return 0;
    }
    mod%=252;
    if(dp[pos][vis][mod][limit]!=-1)//记忆化搜索,如果以及搜过,则不用再搜。 
        return dp[pos][vis][mod][limit];
    int end;
    if(limit)//只有达到最高位,才会受到原数每一位的限制 
        end=mark[pos];
    else
        end=9;
    long long res=0;
    for(int i=0;i<=end;i++)//要枚举0,因为那些小的数,就是前面填充的0 
    {
        if(i!=0)
            res+=dfs(pos+1,vis|(1<<i-1),mod*10+i,limit&&(i==end));//vis相当于是以二进制存了当前数含有哪些数字 
        else                                    //如果一个数是5或8的倍数,那么除最后一位外的前几位除以252的余数 
            res+=dfs(pos+1,vis,mod*10+i,limit&&(i==end));   //再乘以10加上最后一位,那么它也必然是5或8的倍数 
    }                                                   //乘10相当于模的是2520,而5和8的倍数与后三位有关 
    dp[pos][vis][mod][limit]=res;
    return dp[pos][vis][mod][limit];    
}



int main()
{
    //freopen("magic.in","r",stdin);
    //freopen("magic.out","w",stdout);

    k=readlong();l=readlong();r=readlong();
    bit[0]=1;
    for(int i=1;i<20;i++)
        bit[i]=bit[i-1]*10;

    solve(r);
    ans=dfs(1,0,0,true);
    solve(l-1);
    ans-=dfs(1,0,0,true);
    printf("%I64d",ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值