• 洛谷P1037 产生数 题解 搜索


    题目链接:https://www.luogu.com.cn/problem/P1037

    题目描述

    给出一个整数 (n(n<10^{30}))(k) 个变换规则 ((k le 15))
    规则:
    一位数可变换成另一个一位数:
    规则的右部不能为零。

    例如:(n=234)。有规则((k=2)):

    (2)->(5)
    (3)->(6)

    上面的整数(234)经过变换后可能产生出的整数为(包括原数):

    (234)
    (534)
    (264)
    (564)

    (4) 种不同的产生数

    问题:
    给出一个整数 (n)(k) 个规则。
    求出:
    经过任意次的变换((0)次或多次),能产生出多少个不同整数。
    仅要求输出个数。

    输入格式

    键盘输入,格式为:

    (n) (k)
    (x_1) (y_1)
    (x_2) (y_2)
    ... ...
    (x_n) (y_n)

    输出格式

    屏幕输出,格式为:
    (1) 个整数(满足条件的个数)

    样例输入1

    234 2
    2 5
    3 6
    

    样例输出1

    4
    

    题解

    这个问题我们可以转换成图论里面的问题。

    我们可以把 (0)(9)(10) 个数看成 (10) 个点。
    然后对于任意一对关系 (x) -> (y) ,我们从 (x)(y) 连一条边。

    那么我们怎么存这个图呢?
    图论里面最基础的存图方式是 邻接矩阵邻接表

    邻接矩阵 的方法:
    我们开一个 (10 imes 10) 的数组 (g[10][10])
    一开始置所有的 (g[i][j])(false)
    然后如果有一对关系 (x) -> (y) ,则令 (g[x][y])(true)
    这样操作之后,我就知道对于任意一个数 (x) ,它能够直接到达的数的个数,即:所有 (g[x][j])(true) 的数的个数。

    我们这里说的是 (x)(y) 能直接到达是指 (x)(y) 有一条直接可达的边(即 (g[x][y] = true))。
    但是除了直接可达以外,还有间接可达的,比如,如果有两对关系:

    (1) -> (2)
    (2) -> (3)

    那么 (1) 是可以通过 (2) 间接到达 (3) 的。

    那么怎么确定每一个数可达的数的范围呢?比较方便的形式就是 搜索
    我们再开一个bool数组 (vis[10][10])(vis[x][y]) 用于标记 (x)(y) 是否可达(包括直接或间接可达);
    然后开一个函数 dfs(int u, int s) ,其中 (u) 表示当前点, (s) 表示起点,如果当前到达点 (u) ,则置 (vis[s][u])(true),然后对于所有 u 直接可达的点 v ,执行 dfs(v, s)(但是要注意,如果此时 (vis[s][v])(true) ,则不需要访问了,因为递归的访问访问过的点会致使函数陷入死循环)。

    实现的伪代码如下(伪代码就是不可以编译的代码,但是你可以看懂的代码):

    dfs(u, s):
        vis[s][u] = true;
        for 所有u能够直接到达的v:
            if (v没有访问过):
                dfs(v, s);
    

    但是这里遇到一个问题,这个问题是邻接矩阵的效率问题,这个问题体现在:
    如果现在有 (10) 对关系:
    (0) -> (1)
    (1) -> (2)
    ... ...
    (8) -> (9)
    那么如果一开始从 (0) 开始搜索,会搜索 (10) 层,在每一层,对于当前 dfs(u, s) 中的 (u) ,我们需要从 (0)(9) 遍历 (v) ,所以总共需要进行 (10^{10}) 次判断。
    而这个时间是不允许的(一道题的时间复杂度不能超过 (10^9))!

    因为邻接矩阵中存在很多多余的判断,对于任意一个 (u) ,你需要从 (0)(9) 去遍历 (i) 并判断 (g[u][i]) 是否为 (true),来确定 (u)(i) 是否有一条边。这样就有着很多多余的判断。

    那么是否能够对于每一个点 (u) ,我们都用一个东西记录和它邻接的点(即:它能够到达的点)有哪些呢?
    实现的方式有两种:

    1. 链表(链表刚好可以实现这个功能)
    2. 可变数组。

    邻接表 的方法:
    我们在这里使用 STL (C++标准库)中提供给我们的 vector 容器来实现这个功能。
    vector 容器提供给我们的功能就是可变数组的功能。
    使用 vector 之前需要添加头文件:

    #include <vector>
    

    当然这个头文件也是包含在万能头文件中的。

    我们可以通过下面的代码来体会一下 vector 的使用:

    #include <bits/stdc++.h>
    using namespace std;
    vector<int> vec;    // 定义一个int类型的可变数组vec
    int main() {
        for (int i = 1; i <= 5; i ++)
            vec.push_back(i);   // vec内一次push进去1至5
        cout << vec.size() << endl; // vec的大小
        for (int i = 0; i < 5; i ++)
            cout << vec[i] << ",";  // 通过vec[i]获取vec的第i个元素,坐标同样从0开始
        return 0;
    }
    

    输出结果如下:

    5
    1,2,3,4,5,
    

    那么我们可以开一个 vector<int> 类型的数组 (g[10]) ,然后对于每一对关系 (x) -> (y) ,执行:

    g[x].push_back(y);
    

    然后想要知道点 (x) 有哪些直接可达的点 (y) ,可以这样遍历:

    for (int i = 0; i < g[x].size(); i ++) {
        int y = g[x][i];
    }
    

    但是以我多年的经验,每次执行 g[x].size() 效率比较低,所以我们可以一开始开一个变量 sz 来存放 g[x].size() ,然后再进行遍历,实现代码如下:

    int sz = g[x].size();
    for (int i = 0; i < sz; i ++) {
        int y = g[x][i];
    }
    

    这样我们就大致讲解好了 邻接表 的 vector 实现。

    然后我们再开一个 (cnt) 数组, (cnt[x]) 表示 (x) 能够达到的数的数量。

    对于我们的样例:
    因为 (2) 能够到达 (5)(3) 能够到达 (6) ,所以:
    (cnt[2]=2)
    (cnt[3]=2)
    其它的 (cnt) 值都为 (1)

    所以对于 (234) 来说,总的方案数为:
    (cnt[2] imes cnt[3] imes cnt[4] = 2 imes 2 imes 1 = 2)

    因为数据量达到了 (10^{30}) ,用 long long 也存不下,所以得使用高精度乘法(我的代码里面实现了一个很简单的高精度乘法)。

    然后我们结合上面讲到的 dfs 函数,可以实现代码如下:

    #include <bits/stdc++.h>
    using namespace std;
    char s[33];
    int len, ans[33], k, x, y, cnt[10];
    bool vis[10][10];
    vector<int> g[10];
    void dfs(int u, int s) {
        vis[s][u] = true;
        int sz = g[u].size();
        for (int i = 0; i < sz; i ++) {
            int v = g[u][i];
            if (!vis[s][v])
                dfs(v, s);
        }
    }
    void multi(int a) {  // 实现大数和小数的乘法
        for (int i = 0; i < 33; i ++)
            ans[i] *= a;
        for (int i = 0; i < 32; i ++) {
            ans[i+1] += ans[i]/10;
            ans[i] %= 10;
        }
    }
    void output() { // 输出结果
        int i = 32;
        while (i > 0 && ans[i] == 0) i --;
        while (i >= 0) cout << ans[i--];
        cout << endl;
    }
    int main() {
        cin >> s >> k;
        while (k --) {
            cin >> x >> y;
            g[x].push_back(y);
        }
        for (int i = 0; i < 10; i ++)
            dfs(i, i);
        for (int i = 0; i < 10; i ++)
            for (int j = 0; j < 10; j ++)
                cnt[i] += vis[i][j];
        ans[0] = 1;
        int len = strlen(s);
        for (int i = 0; i < len ; i ++)
            multi(cnt[ s[i]-'0' ]);
        output();
        return 0;
    }
    
  • 相关阅读:
    js 压缩 预览 上传图片
    js base64 转成图片上传
    支付宝扫码转账
    js网页 唤醒支付宝
    URL 生成带文字二维码
    iOS-语言本地化
    iOS-Storyboad动态刷新
    iOS-UITouch,UIEvent使用介绍
    JSP-标准动作标记
    JSP-注释,脚本元素,指令
  • 原文地址:https://www.cnblogs.com/quanjun/p/11994331.html
Copyright © 2020-2023  润新知