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)    收藏  举报

导航