洛谷 P1037 [NOIP 2002 普及组] 产生数 题解
洛谷 P1037 [NOIP 2002 普及组] 产生数 题解
题目描述
原题链接:洛谷 P1037 [NOIP 2002 普及组] 产生数
给出一个整数 \(n\) 和 \(k\) 个变换规则。
规则:
- 一位数可变换成另一个一位数。
- 规则的右部不能为零。
现在给出一个整数 \(n\) 和 \(k\) 个规则。求出经过任意次的变换(\(0\) 次或多次),能产生出多少个不同整数。(\(n \lt 10^{30}\),\(k \le 15\))
题意分析
学完 DFS 和 BFS 之后,我就做这道题。蒟蒻做了整半天,结果40pts,于是默默地按下了查看题解。
这道题其实就是图论与高精度的综合运用。
一、图的输入与遍历
使用邻接表存储输入数据。例如,当输入:
1234 3
2 3
3 2
3 5
我们可以构造出如下的有向图:

要想知道数字 \(2\) 可以变成哪些点,直接以数字 \(2\) 为起点遍历这个图,并统计经过的顶点数量即可。根据乘法原理,将 \(n\) 的每一位数字的遍历结果相乘即为方案总数。
示例代码
- 深度优先遍历
#include <vector>
using namespace std;
vector<int> G[10]; // 因为只有 10 个一位数,所以图也只有 10 个顶点
bool vis[10]; // vis[i]=0 表示 i 没有被访问过,vis[i]=1 表示 i 被访问过
int dfs(int x) {
vis[x] = 1;
int cnt = 1;
for (int i = 0; i < G[x].size(); i++)
if (!vis[G[x][i]])
cnt += dfs(G[x][i]); // 如果 G[x][i] 没有被访问过,则访问这个数
return cnt;
}
- 广度优先遍历
#include <vector>
#include <queue>
using namespace std;
vector<int> G[10];
queue<int> Q;
bool vis[10];
int bfs(int x) {
vis[x] = 1;
int cnt = 0;
while (!Q.empty()) {
int y = Q.front();
Q.pop();
cnt++;
for (int i = 0; i < G[y].size(); i++)
if (!vis[G[y][i]]) {
vis[G[y][i]] = 1;
Q.push(G[y][i]);
}
}
return cnt;
}
二、高精度乘法
每一位数字最多有 \(10\) 种变换方式,\(n\) 最多有 \(30\) 位,所以方案总数最多为 \(10^{30}\),即便是 unsigned long long 也存不下这么大的数字,所以我们需要使用高精度乘低精度。
我们使用封装结构体并重载运算符的方式来编写程序,使用时更加简洁。
struct Bigint {
int len, a[35]; // 为了兼顾效率与代码复杂度,用 len 记录位数,a 记录每个数位
Bigint(int x = 0) { // 通过初始化使得这个大整数能够表示整型 x,默认为 0
memset(a, 0, sizeof(a));
for (len = 1; x; len++)
a[len] = x % 10, x /= 10;
len--;
}
int &operator[](int i) {
return a[i]; // 重载[], 可以直接用 x[i] 代表 x.a[i], 编写时更加自然
}
void flatten(int L) { //一口气处理 1 到 L 范围内的进位并重置长度。需要保证 L 不小于有效长度
// 因为相当于把不是一位数的位都处理成一位数, 故取名为“展平”
len = L;
for (int i = 1; i <= len; i++)
a[i + 1] += a[i] / 10, a[i] %= 10;
for (; !a[len]; ) // 重置长度成为有效长度
len--;
}
void print() { // 输出
for (int i = max(len, 1); i >= 1; i--)
printf("%d", a[i]);
}
};
Bigint operator*(Bigint a, int b) { // 表示 Bigint 类乘整型变量, 返回一个 Bigint 类
Bigint c;
int len = a.len;
for (int i = 1; i <= len; i++)
c[i] = a[i] * b; // 计算贡献
c.flatten(len + 2); // dfs 函数返回值最多 2 位, 所以可以这样做一遍“展平”处理进位
return c;
}
代码实现
#include <bits/stdc++.h>
using namespace std;
struct Bigint {
int len, a[35];
Bigint(int x = 0) {
memset(a, 0, sizeof(a));
for (len = 1; x; len++)
a[len] = x % 10, x /= 10;
len--;
}
int &operator[](int i) {
return a[i];
}
void flatten(int L) {
len = L;
for (int i = 1; i <= len; i++)
a[i + 1] += a[i] / 10, a[i] %= 10;
for (; !a[len]; )
len--;
}
void print() {
for (int i = max(len, 1); i >= 1; i--)
printf("%d", a[i]);
}
};
Bigint operator*(Bigint a, int b) {
Bigint c;
int len = a.len;
for (int i = 1; i <= len; i++)
c[i] = a[i] * b;
c.flatten(len + 2);
return c;
}
vector<int> G[10];
queue<int> Q;
bool vis[10];
int n[40], k, len;
char n1[40];
Bigint ans(1);
int dfs(int x) {
vis[x] = 1;
int cnt = 1;
for (int i = 0; i < G[x].size(); i++)
if (!vis[G[x][i]])
cnt += dfs(G[x][i]); // 如果 G[x][i] 没有被访问过,则访问这个数
return cnt;
}
int bfs(int x) {
vis[x] = 1;
Q.push(x);
int cnt = 0;
while (!Q.empty()) {
int y = Q.front();
Q.pop();
cnt++;
for (int i = 0; i < G[y].size(); i++)
if (!vis[G[y][i]]) {
vis[G[y][i]] = 1;
Q.push(G[y][i]);
}
}
return cnt;
}
int main() {
scanf("%s%d", n1, &k);
len = strlen(n1);
for (int i = 0; i < len; i++)
n[i] = n1[i] - '0';
for (int i = 0; i < k; i++) {
int x, y;
scanf("%d%d", &x, &y);
G[x].push_back(y);
}
for (int i = 0; i < len; i++) {
memset(vis, 0, sizeof(vis));
ans = ans * dfs(n[i]);
// ans = ans * bfs(n[i]);
}
ans.print();
return 0;
}

浙公网安备 33010602011771号