洛谷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 \times 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\) ,我们都用一个东西记录和它邻接的点(即:它能够到达的点)有哪些呢?
实现的方式有两种:
- 链表(链表刚好可以实现这个功能)
- 可变数组。
邻接表 的方法:
我们在这里使用 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] \times cnt[3] \times cnt[4] = 2 \times 2 \times 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;
}
洛谷P1037 产生数 题解 搜索的更多相关文章
- 洛谷P1036 选数 题解 简单搜索/简单状态压缩枚举
题目链接:https://www.luogu.com.cn/problem/P1036 题目描述 已知 \(n\) 个整数 \(x_1,x_2,-,x_n\) ,以及 \(1\) 个整数 \(k(k& ...
- 洛谷 P1037 产生数
题目描述 给出一个整数n(n<10^30)和k个变换规则(k≤15). 规则: 一位数可变换成另一个一位数: 规则的右部不能为零. 例如:n=234.有规则(k=2): 2->53-> ...
- 【洛谷P3960】列队题解
[洛谷P3960]列队题解 题目链接 题意: Sylvia 是一个热爱学习的女孩子. 前段时间,Sylvia 参加了学校的军训.众所周知,军训的时候需要站方阵. Sylvia 所在的方阵中有 n×m ...
- 洛谷P2507 [SCOI2008]配对 题解(dp+贪心)
洛谷P2507 [SCOI2008]配对 题解(dp+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/1299251 链接题目地址:洛谷P2507 [S ...
- 洛谷P2832 行路难 分析+题解代码【玄学最短路】
洛谷P2832 行路难 分析+题解代码[玄学最短路] 题目背景: 小X来到了山区,领略山林之乐.在他乐以忘忧之时,他突然发现,开学迫在眉睫 题目描述: 山区有n座山.山之间有m条羊肠小道,每条连接两座 ...
- 洛谷P1102 A-B数对
洛谷P1102 A-B数对 https://www.luogu.org/problem/show?pid=1102 题目描述 出题是一件痛苦的事情! 题目看多了也有审美疲劳,于是我舍弃了大家所熟悉的A ...
- 洛谷P2312 解方程题解
洛谷P2312 解方程题解 题目描述 已知多项式方程: \[a_0+a_1x+a_2x^2+\cdots+a_nx^n=0\] 求这个方程在 \([1,m]\) 内的整数解(\(n\) 和 \(m\) ...
- 洛谷P1577 切绳子题解
洛谷P1577 切绳子题解 题目描述 有N条绳子,它们的长度分别为Li.如果从它们中切割出K条长度相同的 绳子,这K条绳子每条最长能有多长?答案保留到小数点后2位(直接舍掉2为后的小数). 输入输出格 ...
- 洛谷P1288 取数游戏II(博弈)
洛谷P1288 取数游戏II 先手必胜的条件需要满足如下中至少 \(1\) 条: 从初始位置向左走到第一个 \(0\) 的位置,经过边的数目为偶数(包含 \(0\) 这条边). 从初始位置向右走到第一 ...
随机推荐
- HZOJ 分组
打了好多个代码. 对于测试点1,11:手动模拟. void QJ1_11() { ) { int tk; ]+a[]))tk=; ; if(tk<=k) { puts("); puts ...
- JAVA之NIO按行读写大文件,完美解决中文乱码问题
;//一次读取的字节长度 File fin = new File("D:\\test\\20160622_627975.txt");//读取的文件 File fout = new ...
- 2007年NOIP普及组复赛题解
题目涉及算法: 奖学金:结构体排序: 纪念品分组:贪心: 守望者的逃离:动态规划: Hanoi 双塔问题:递推. 奖学金 题目链接:https://www.luogu.org/problem/P109 ...
- codeforces1217-edu
C The Number Of Good Substrings 我原来的基本思路也是这样,但是写的不够好 注意算前缀和的时候,字符串起始最好从1开始. #include<cstdio> # ...
- Python--day48--ORM框架SQLAlchemy操作表
ORM框架SQLAlchemy操作表: 表结构和数据库连接: #!/usr/bin/env python # -*- coding:utf-8 -*- from sqlalchemy.ext.decl ...
- Tensorflow安装问题: Could not find a version that satisfies the requirement tensorflow pip命令
引言: Tensorflow大名鼎鼎,这里不再赘述其为何物.这里讲描述在安装python包的时候碰到的“No matching distribution found for tensorflow”,其 ...
- 总结:关于留学网站使用laravel框架的总结
1.从git库中clone后本地项目根目录没有vendor文件夹,安装composer 2.composer install 报错 ,删除 composer.lock 文件,重新执行 composer ...
- H3C Hosts文件
- linux 阻塞 open 作为对 EBUSY 的替代
当设备不可存取, 返回一个错误常常是最合理的方法, 但是有些情况用户可能更愿意等待 设备. 例如, 如果一个数据通讯通道既用于规律地预期地传送报告(使用 crontab), 也用于根据 用户的需要偶尔 ...
- linux单 open 设备
提供存取控制的强力方式是只允许一个设备一次被一个进程打开(单次打开). 这个技术最 好是避免因为它限制了用户的灵活性. 一个用户可能想运行不同的进程在一个设备上, 一 个读状态信息而另一个写数据. 在 ...