P1037 [NOIP 2002 普及组] 产生数
题目描述
给出一个整数 \(n\) 和 \(k\) 个变换规则。
规则:
- 一位数可变换成另一个一位数。
- 规则的右部不能为零。
...
现在给出一个整数 \(n\) 和 \(k\) 个规则。求出经过任意次的变换(\(0\) 次或多次),能产生出多少个不同整数。仅要求输出个数。
输入格式
第一行两个整数 \(n,k\),含义如题面所示。接下来 \(k\) 行,每行两个整数 \(x_i,y_i\),表示每条规则。
输出格式
共一行,输出能生成的数字个数。
说明/提示
对于 \(100\%\) 数据,满足 \(n \lt 10^{30}\),\(k \le 15\)。
解析
本题的数据范围很大(\(0 \leq \text{ans} \leq 10^{30}\))。这里我们可以使用 __int128 水过此题。(__int128 需要自己写输出(其实就是快写)。)具体的方法就是对字符串的每一位进行 dfs,每一次 dfs 都判断一次规则有没有符合这一位的,如果有就继续 dfs。我们同时使用一个 vis[] 数组来标记已经取过多少个位数,哪些可以取,哪些不能取。然后对字符串的每一位 dfs 即可,每一次成功 dfs 每位数的计数器加一。根据乘法原理,每一位数可能性总数乘在一起就是最终的结果。
为什么 \(0 \leq \text{ans} \leq 10^{11}\) 呢?我们看数据范围:\(n \lt 10^{30}\);那么就最多会有 \(31\) 位的数字。那么最多的可能性(就是所有的位数 \(10\) 种可能性都有)就是 \(10^{30}\)。所以正常 long long \(2^{64}-1\) 的范围是一定过不了的。而 __int128 的范围 \(2^{128}-1\) 就可以过掉此题。所以正常说这道题需要实现高精度乘法(具体是在乘答案的那一步 ans *= cnt)。要是数据范围不大的话,这道题就是橙题了。
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
using namespace std;
using ll = long long;
using lll = __int128;
ll k, x[20], y[20], cnt;
lll ans = 1;
bool vis[10] = {false};
string n;
void dfs(int rt){
if(vis[rt]) return;
vis[rt] = true;
++cnt;
for(int i=1; i<=k; ++i)
if(x[i] == rt)
dfs(y[i]);
return;
}
void print(lll num){
if(num < 0) putchar('-'), num = -num;
if(num > 9) print(num/10);
putchar(num % 10 + '0');
}
int main(){
cin >> n >> k;
for(int i=1; i<=k; ++i)
cin >> x[i] >> y[i];
for(int i=0; i<n.size(); ++i){
memset(vis, 0, sizeof vis);
cnt = 0;
dfs(n[i]-'0');
ans *= cnt;
}
print(ans);
return 0;
}
posted on 2025-10-26 14:31 符星珞-Astralyn 阅读(8) 评论(0) 收藏 举报
浙公网安备 33010602011771号