• 假的数论gcd,真的记忆化搜索(Codeforce 1070- A. Find a Number)


    题目链接:

    原题:http://codeforces.com/problemset/problem/1070/A

    翻译过的训练题:https://vjudge.net/contest/361183#problem/A

    题目大意:

    给你两个正整数p和x,让你求出最小的正整数m,满足m被p整除且m的各数位之和为x。

    Input

    仅含两个整数p和x(1<=p<=500, 1<=x<=5000).

    Output

    输出最小的正整数m,若无解则输出-1.

    Examples

    Input
    13 50
    Output
    699998
    Input
    61 2
    Output
    1000000000000000000000000000001
    Input
    15 50
    Output
    -1

    思路:

    这个题目,按位数求和,乍一眼看上去很容易想到HDU的这个题 http://acm.hdu.edu.cn/showproblem.php?pid=1554,一开始也以为是数论题,

    但是一看到output里面有一个位数超过unsigned long long 接受的范围,就知道这题不一样。他求出来的,解,一定是一组位数数组an,然后连续打印得到结果

    那求解某数,满足位数和,整除数,除了用能够运算的数解方程,还能怎么样?——穷举,每一个位数,【0,9】都试一试,类似数位dp的办法,

    那这题用什么方式穷举呢?这题对时间有要求,目标数位是未知的,层数不知道,也就是说不能多层循环,那就考虑搜索,保证效率,

    最重要的是,他要最小的,那一定要按照”层优先“来遍历,因为位数决定大小,dfs可能会先搜到长度更大,数更大而满足条件的,因此用bfs

    由于最后要把宽搜的所有路径记录下来,且得到的an如果满足恰好能被p整除,一定要每次位数都检查,由于一位一位来检查,需要把上一位得到的余数记住

    • 即:now(mod)=(pre(mod)*10+an)% p,now(sum)=pre(sum)+ an
    • 又:观察到输入值极限为,500,5000,即求和不超过5000,超过了还找不到目标的数,即,不存在,输出 -1
    • 有可能an不一样,但是各位求和一样。又有可能求和满足,但是不能整除,此时必有余数,余数可能有相同的,但是求和不满足
    • 但是余数相同,求和相同的一组结果不是唯一的,比如3的次方数,能被整除的要求恰好就是各个位数和也能被整除,因此找到雷同的,可以先排除

    设 vis【mod】【b】,a表示余数,b表当前位数和,作为标记,最先找到满足解的,从a1开始试【1,9】,后面的试【0,9】,一定是最小的,

    将走过的路径的数记下来,即,ai到ai+1,即各个位数记下,即可

    所以需要的元素是:mod,sum,an和len(n的值,当前第几层),标记的,vis【mod】【b】

    附上代码(含步骤解释):

    #include<iostream>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <queue>
    
    typedef long long ll;
    using namespace std;
    const int n=505,m=5005;
    int  p,x;
    bool vis[n][m];//记录是否走过, 走过成"1"
    
    struct node {//记录路径
    
        int len;//当前第几数位
        int sum;//当前数位和.
        int mod;//当前余数和,要一个一个加进去,
    
        //上一个的值要被下一个利用才可以得到下一个的
    
        node();
        char ans[5000];//节省空间
    } head,tail;//从头到尾部,表示前一位h->t
    //也可以开一个p[a][]
    node::node() {
        len=-1,sum=0,mod=0;
        memset(ans,0,sizeof(ans));//初始化
    
    }
    
    void bfs() {
        queue<node>q;//倒序输出,队列
        q.push(head);//头部入列,
        while(!q.empty()) {//列为空,且之前没有新的tail压入queue,
            //遍历求和范围超过了x,宽搜层数达到了极限--无解
            head=q.front();//路径头部更新, 回到上一层,某层ans=ix队首压入,然后讨论
            q.pop(); //弹出这一层//也可以写在后面
            if(head.mod==0&&head.sum==x) {
                cout<<head.ans<<endl;//有解
                return;
            }
            for(int i=0; i<=9; i++) {//遍历这一层,宽度为位数0,9
                tail=head;//路径尾部更新,回到上一层
                tail.len++;//长度更新,深度更新,第几位数
                tail.sum+=i;//和更新
                tail.mod=(tail.mod*10+i)%p;//余数更新
                tail.ans[tail.len]=i+'0';// 更新当前这一层/长度/位数的值--0,9都试一次,
                if(tail.sum<=x&&vis[tail.mod][tail.sum]==0) {
                    //如果遍历过就直接排除,不更新,
                    //如果sum超过了x直接跳过,不更新路径
                    //tail=head不会和之前有区别
                    vis[tail.mod][tail.sum]=1;//标记表示遍历过了
                    q.push(tail);//更新路径队列,压入尾部,
                    //表示这个位数为"i"时可以继续往下讨论,
                    //用queue记住还有可能讨论的值
    
                }
    
            }
        }
        cout<<-1<<endl;//如果队列最终为空,必为无解
    }
    
    int main() {
        while(cin>>p>>x) {
            memset(vis,0,sizeof(vis));
            bfs();
        }
        return 0;
    }
    View Code

    虽然AC,但是又产生一个疑问:字符数组的长度是否有范围,显然如果输入x=5000,有5000位数都是1,就可以达到,剩下的全为0即可,那不是位数无穷了吗?

    现在是运气好,所以这里字符串是有re危险的,其实最好设成vector,至于会不会超过vector,emmm毕竟能做出来,应该有一个位数极限,可以试出来

    (待续)

    老实一点,可爱多了
  • 相关阅读:
    App分享微信小程序
    PHP-FFMpeg 操作视频/音频文件 (转)
    用户画像
    phpcms中的RBAC权限系统
    PHPExcel生成excel
    OPNET中节点模型中包流的索引号的含义
    删除opnet之前保存或打开的目录后每次打开总会提示warning
    opnet 的学习方法有感
    win10:两款轻量级美化软件使用技巧(StartlsBack++与RocketDock)
    任务栏透明
  • 原文地址:https://www.cnblogs.com/KID-yln/p/12495853.html
Copyright © 2020-2023  润新知