UVA11732 "strcmp()" Anyone?

UVA11732 "strcmp()" Anyone?

题目传送门

一个我认为比较有趣的问题……

题意

给出 \(n\) 个字符串,两两比较字典序大小,求出所需比较的总次数并输出。

分析

使用 trie 树(字典树)来统计给定字符串集合中所有字符串的前缀子串出现次数之和。

trie 树是一种多叉树数据结构,用于高效地存储和检索字符串集合。每个节点代表一个字符,从根节点开始,通过边上的字符逐层向下遍历,构成一个字符串。

这里的 trie 树是一个特殊的字典树,它的每个节点都有一个整数数组 \(tree\),用于保存子节点的索引。数组的长度是 \(65\),因为这里的字符串包含小写字母、大写字母和数字,共

\[26 + 26 + 10 + 1 + 1 + 1= 65 \]

代码的关键部分是 Insert 函数,它用于将一个字符串插入到 trie 树中,并统计前缀子串出现次数之和。函数具体步骤如下:

从根节点开始,初始化当前节点的索引 \(id\)\(0\),并将全局变量 \(ans\) 初始化为 \(0\),用于记录前缀子串出现次数之和。

将空字符串的计数值加到 \(ans\) 中,表示空字符串是所有字符串的前缀。

依次遍历字符串中的每个字符:

  1. 将字符映射成一个整数 \(c\),用于在 \(tree\) 数组中索引子节点。

  2. 如果当前节点的子节点中不存在字符 \(c\),则创建一个新的子节点,并更新当前节点的索引为新节点的索引,并将该节点计数和结尾计数初始化为 \(0\)

  3. 更新当前节点的索引 \(id\) 为新的子节点索引。

  4. 将当前节点的计数值加到 \(ans\) 中,表示经过当前节点的前缀子串出现次数,这里加两次是因为前缀出现在当前节点和其所有父节点上。

  5. 更新当前节点的计数值,表示经过该节点的次数。
    遍历完字符串后,更新当前字符串的结尾节点的计数和结尾计数,并将其计数值加到 \(ans\) 中,表示当前字符串在最后一个节点处结束,该节点记录了所有以此字符串结尾的子串出现的次数。

代码的 main 函数中通过循环不断输入字符串,并调用 Insert 函数将字符串插入到 trie 树中。最后输出统计结果。

这样,通过 trie 树的遍历和计数机制,代码可以高效地统计给定字符串集合中所有字符串的前缀子串出现次数之和。

接下来贴上代码。

AC Code

#include<bits/stdc++.h>
using namespace std;
int tree[4000005][65], val[4000005], End[4000005]; // trie树节点信息
int change(char c) // 字符转换函数,将字符映射到整数
{
    if (c >= 'a' && c <= 'z') return c - 'a';     // 映射小写字母a-z到0-25
    if (c >= 'A' && c <= 'Z') return c - 'A' + 26; // 映射大写字母A-Z到26-51
    return c - '0' + 52;                         // 映射数字0-9到52-61
}

int cnt;         // trie树的节点计数
long long ans;   // 统计前缀子串出现次数之和
void Insert(char *s) // 将字符串s插入trie树
{
    int id = 0, l = strlen(s); // id为当前节点索引,l为字符串s的长度
    ans += val[0];             // 统计空字符串的出现次数,即所有字符串的前缀
    val[0]++;                  // 空字符串计数加一
    for (int i = 0; i < l; ++i)
    {
        int c = change(s[i]); // 将字符s[i]转换为整数
        if (!tree[id][c])     // 如果当前节点的子节点不存在
        {
            memset(tree[cnt], 0, sizeof(tree[cnt])); // 初始化子节点信息
            val[cnt] = 0;                            // 子节点计数初始化为0
            End[cnt] = 0;                            // 子节点结尾计数初始化为0
            tree[id][c] = cnt++;                     // 在当前节点创建子节点并更新id为子节点索引
        }
        id = tree[id][c];       // 移动到子节点
        ans += 2LL * val[id];   // 更新统计值,当前节点的计数值加到结果上,每次统计两次,是因为前缀出现在当前节点和当前节点的父节点(父节点的父节点...)上
        val[id]++;              // 更新当前节点的计数值,表示经过当前节点的次数
    }
    ans += End[id]; // 更新统计值,当前字符串在最后一个节点处结束,该节点记录了所有以此字符串结尾的子串出现的次数
    End[id]++;      // 更新该节点的结尾计数值,表示以此字符串结尾的子串出现次数加一
}

void init() // 初始化trie树
{
    cnt = 1;                   // 初始化节点计数为1(根节点已经创建)
    ans = 0;                   // 初始化结果统计为0
    memset(tree[0], 0, sizeof(tree[0])); // 初始化根节点信息
    val[0] = End[0] = 0;       // 根节点计数和结尾计数初始化为0
}

char s[1005]; // 用于存储输入的字符串
int main()
{
    int n, i, ca = 1;
    while (~scanf("%d", &n) && n) // 输入n个字符串进行处理,直到n为0
    {
        init(); // 初始化trie树
        for (i = 0; i < n; ++i)
        {
            scanf("%s", s); // 输入字符串s
            Insert(s);      // 将字符串s插入trie树,并统计前缀子串出现次数之和
        }
        printf("Case %d: %lld\n", ca++, ans); // 输出结果
    }
    return 0;
}

posted @ 2023-08-05 08:26  XLoffy  阅读(9)  评论(0编辑  收藏  举报