1 条题解

  • 0
    @ 2025-8-24 22:46:28

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar wloving
    古之立大事者,不惟有超世之才,亦必有坚忍不拔之志。

    搬运于2025-08-24 22:46:28,当前版本为作者最后更新于2024-05-08 01:46:01,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    题目分析

    仔细阅读题目,可知题目要求的是对于每个 aia_ij=1ng(ai,aj)\sum\limits_{j=1}^ng(a_i,a_j) 。再结合 g(a,b)g(a,b) 的定义,可知,对于 aia_i 来说,我们需要计算 aia_ia1ana_1\sim a_n 构成的 nn 组数对的 g(ai,aj)g(a_i,a_j) 的总和。对于 g(a,b)g(a,b) 的值,则是 [min(a,b),max(a,b)][\min(a,b),\max(a,b)] 中的最小 f(c)f(c) 的值,当前 c[min(a,b),max(a,b)]c\in [\min(a,b),\max(a,b)]。对于 f(c)f(c) 的作用则是返回将小数 cc 变成整数的乘 1010 的次数,即 cc 的小数部分的位数。题目描述的比较绕,需要耐心将公式之间的关系捋清楚。

    接下来思考如何计算 g(a,b)g(a,b)。我们分情况进行讨论,为方便描述我们设 aba\le b

    1. 两个数的整数部分不同。

    当整数部分不相同时,在 [a,b][a,b] 中一定可以存在一个整数 cc ,当 cc 为整数时,f(c)=0f(c)=0。所以整数部分不相同,g(a,b)=0g(a,b)=0

    1. 两个数的整数部分相同。当整数部分相同时,考虑小数部分。可以分成三种情况:

      1. 小数部分完全不一样。

        11.12311.12311.23111.231 对应的 c=11.2c=11.2f(c)=1f(c)=1。也就是较小的数值保留1位小数再加 0.10.1 就好,这样乘 10110^1 就能成为整数。

      2. 部分前缀相同。

        11.1234511.1234511.1238811.12388 对应的 c=11.1235c=11.1235f(c)=4f(c)=4。也就是先找 aabb 的最大公共前缀,再往后一位,找一个在两者范围内的数字即可,若小数点后最大公共前缀长度为 kk 则乘 10k+110^{k+1} 就能成为整数。

      3. 较小的数字是较大数字的前缀。

        11.12311.12311.12345611.123456 对应的 c=11.123c=11.123f(c)=3f(c)=3。也就是较小的那个数字的小数位数,若较小数字的小数位置为 kk 则乘 10k10^k 就能成为整数。

    此时我们可以发现,问题是围绕着公共前缀展开的。对于小数的公共前缀,我们可以将小数视为字符串进行存储与使用,就将问题转换了字符串的公共前缀问题,这类问题使我们联想到字典树,可以考虑使用字典树解决它。

    以样例2为例,讲解字典树解法思路。

    8
    1.1145
    1.114
    1.1145
    1.514
    1.19198
    1.1154
    1.114
    1.1145
    

    下图为建立好的字典树。

    1715103837347.png

    查找数值 1.11451.1145 的答案过程。

    1715102902356.png

    查找数值 1.1141.114 的答案过程。

    1715103185147.png

    查找数值 1.5141.514 的答案过程。1715103398110.png

    模拟过程中可发现,在遍历字典树的过程中,我们计算新增的不同前缀的元素个数与当前结尾的元素个数,它们成为整数的答案为当前小数位数。另外,在遍历结束后需要考虑和计算以当前数字为前缀且又比它长的数字个数,详细可查看寻找数值 1.1141.114 的过程。

    代码实现

    #include <bits/stdc++.h>
    using namespace std;
    using i64 = long long;
    const int N = 1e5 + 5;
    const int M = 3e6 + 5;
    int n, tot, zs;
    string str[N];
    struct node {
      int son[11];
      // 前缀相同的字符串个数、以该节点结尾的字符串个数、小数部分的位数
      int num, end, dep;
    } trie[M];
    int toNum[256];
    void init() {
      for (int i = 0; i < 10; i++) {
        toNum[i + '0'] = i;
      }
      toNum['.'] = 10;
    }
    void insert(string s) {  // 字典树插入
      int len = s.size();
      int u = 0;
      int dot = -1;   // 小数点位置
      trie[u].num++;  // 记录字符串总数
      for (int i = 0; i < len; i++) {
        int ch = toNum[s[i]];
        if (!trie[u].son[ch]) trie[u].son[ch] = ++tot;
        u = trie[u].son[ch];
        trie[u].num++;
        if (ch == 10) dot = i;                 // 记录小数点的位置
        if (dot != -1) trie[u].dep = i - dot;  // 更新小数部分对应的位数
      }
      trie[u].end++;//记录该处结尾的数字个数
    }
    int findStr(string s) {
      // 整数部分不一样,f(c)为0
      // 整数部分相同时考虑小数部分的最大公共前缀
      int len = s.size();
      int sum = 0, re = trie[0].num;  // re-剩余前缀相同的字符串数量
      int u = 0;
      for (int i = 0; i < len; i++) {
        int ch = toNum[s[i]];
        u = trie[u].son[ch];
        //   num=新增的前缀不同的字符串数量 + 以该节点结尾的字符串数量
        int num = re - trie[u].num + trie[u].end;
        sum += num * trie[u].dep;  // 更新总和
        re -= num;
      }
      // 处理前缀与s相同,但又比s长的字符串
      sum += (trie[u].num - trie[u].end) * trie[u].dep;
      return sum;
    }
    
    int main() {
      ios::sync_with_stdio(false);
      cin.tie(nullptr);
      init();
      string s;
      char c;
      cin >> n;
      for (int i = 1; i <= n; i++) {
        cin >> str[i];
        insert(str[i]);
      }
      for (int i = 1; i <= n; i++) {
        cout << findStr(str[i]) << '\n';
      }
      return 0;
    }
    
    • 1

    信息

    ID
    8420
    时间
    1000ms
    内存
    512MiB
    难度
    4
    标签
    递交数
    0
    已通过
    0
    上传者