zoukankan      html  css  js  c++  java
  • 假的数论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毕竟能做出来,应该有一个位数极限,可以试出来

    (待续)

    老实一点,可爱多了
  • 相关阅读:
    Smart Client Architecture and Design Guide
    Duwamish密码分析篇, Part 3
    庆贺发文100篇
    .Net Distributed Application Design Guide
    New Introduction to ASP.NET 2.0 Web Parts Framework
    SPS toplevel Site Collection Administrators and Owners
    来自Ingo Rammer先生的Email关于《Advanced .Net Remoting》
    The newsletter published by Ingo Rammer
    深度探索.Net Remoting基础架构
    信道、接收器、接收链和信道接受提供程序
  • 原文地址:https://www.cnblogs.com/KID-yln/p/12495853.html
Copyright © 2011-2022 走看看