Codeforces Round #434 D. Polycarp's phone book (字典树)

本文介绍了一种算法,用于从一系列电话号码中找出每个号码的最短唯一子串,确保输入该子串时能在联系人列表中唯一匹配到该号码。

There are n phone numbers in Polycarp's contacts on his phone. Each number is a 9-digit integer, starting with a digit different from 0. All the numbers are distinct.

There is the latest version of Berdroid OS installed on Polycarp's phone. If some number is entered, is shows up all the numbers in the contacts for which there is a substring equal to the entered sequence of digits. For example, is there are three phone numbers in Polycarp's contacts: 123456789100000000 and 100123456, then:

  • if he enters 00 two numbers will show up: 100000000 and 100123456,
  • if he enters 123 two numbers will show up 123456789 and 100123456,
  • if he enters 01 there will be only one number 100123456.

For each of the phone numbers in Polycarp's contacts, find the minimum in length sequence of digits such that if Polycarp enters this sequence, Berdroid shows this only phone number.

Input

The first line contains single integer n (1 ≤ n ≤ 70000) — the total number of phone contacts in Polycarp's contacts.

The phone numbers follow, one in each line. Each number is a positive 9-digit integer starting with a digit from 1 to 9. All the numbers are distinct.

Output

Print exactly n lines: the i-th of them should contain the shortest non-empty sequence of digits, such that if Polycarp enters it, the Berdroid OS shows up only the i-th number from the contacts. If there are several such sequences, print any of them.

Examples
input
3
123456789
100000000
100123456
output
9
000
01
input
4
123456789
193456789
134567819
934567891
output
2
193
81
91

题意:给你一些电话号码,让你找出每一个电话号码的最短的子串,能唯一的找到这个电话号码,就是找出每个串的最短且不是其他串的子串 的子串。


可以用字典树存下每个串的所有后缀(插入的时候把每个后缀的所有前缀的个数都记录),然后查询的时候对于一个串的所有后缀,找出字典树中第一个不存在于其他串中的前缀,然后取长度最短的那个。


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long

using namespace std;

int mx;
char a[70010][10],temp[10],ans[10];

struct trie
{
    trie *next[10];
    int cnt;
    trie()
    {
        memset(next,0,sizeof(next));
        cnt=0;
    }
};
trie *root;
void insert(char *s)
{
    trie *p=root;
    int i,k;
    for(i=0;s[i]!='\0';++i)
    {
        k=s[i]-'0';
        if(p->next[k]==NULL)
            p->next[k]=new trie();
        p=p->next[k];
        p->cnt++;
    }
}

void Delete(char *s)
{
    trie *p = root;
    int i,k;
    for(i=0;s[i]!='\0';i++)
    {
        k = s[i]-'0';
        p = p->next[k];
        p->cnt--;
    }
}
int query(char *s)
{
    trie *p = root;
    int len = 0;
    for(int i=0;s[i]!='\0';i++)
    {
        int x = s[i] - '0';
        temp[len++] = s[i];
        p = p->next[x];
        if(p->cnt == 0)
        {
            if(len < mx)
            {
                mx = len;
                temp[len] = '\0';
                strcpy(ans,temp);
                break;
            }
        }
    }

}
void del(trie *p)
{
    for(int i=0;i<10;i++)
        if(p->next[i]!=NULL)
            del(p->next[i]);
    free(p);
}

int main(void)
{
    int n,i,j;
    while(scanf("%d",&n)==1)
    {
        root=new trie();
        for(i=1;i<=n;i++)
        {
            scanf("%s",a[i]);
            for(j=0;j<9;j++)
                insert(a[i]+j);
        }
        for(i=1;i<=n;i++)
        {
            mx = 10;
            for(j=0;j<9;j++)
                Delete(a[i]+j);
            for(j=0;j<9;j++)
                query(a[i]+j);
            for(j=0;j<9;j++)
                insert(a[i]+j);
            printf("%s\n",ans);
        }

        del(root);
    }

    return 0;
}


### 字典树(Trie)的数据结构实现与应用 #### 什么是字典树字典树,也被称为 Trie 或者前缀树,是一种特殊的树形数据结构,主要用于高效地存储和检索字符串集合中的键[^1]。它通过共享公共前缀来减少重复字符的存储需求,从而节省空间并提高查询效率。 #### 字典树的核心定义 字典树通常由节点组成,每个节点包含两个主要部分: - **isEndOfWord**: 表示当前节点是否是一个单词的结尾。 - **children[]**: 存储指向子节点的指针数组,大小取决于字符集范围(如英文字母则为26)。如果某个位置不为空,则表示存在对应字母作为后续路径的一部分[^2]。 以下是基于 C++ 的基本字典树节点定义代码: ```cpp const int SIZE = 26; struct TrieNode { bool isEndOfWord; TrieNode* children[SIZE]; // 构造函数初始化 TrieNode() : isEndOfWord(false) { for (int i = 0; i < SIZE; ++i) { children[i] = nullptr; } } }; ``` #### 插入操作 向字典树中插入一个新的字符串涉及遍历其各个字符,并逐步构建相应的路径。如果没有匹配的子节点,则创建新的节点;当整个字符串都被处理完毕时,标记最后一个节点为 `isEndOfWord`。 ```cpp void insert(TrieNode* root, const std::string& word) { TrieNode* current = root; for (char ch : word) { int index = ch - &#39;a&#39;; if (!current->children[index]) { current->children[index] = new TrieNode(); } current = current->children[index]; } current->isEndOfWord = true; } ``` #### 查询操作 为了判断某字符串是否存在于字典树中,可以按照相同的方式逐级访问各层节点。只有到达最后一位且该位被标记为结束标志才返回成功。 ```cpp bool search(TrieNode* root, const std::string& word) { TrieNode* current = root; for (char ch : word) { int index = ch - &#39;a&#39;; if (!current->children[index]) { return false; } current = current->children[index]; } return current && current->isEndOfWord; } ``` #### 应用场景 字典树广泛应用于多种实际问题解决之中,例如但不限于以下几种情况: - 文本编辑器中的自动补全功能; - 搜索引擎关键词提示服务; - IP 地址路由表管理; - 基因序列分析等领域内的模式匹配任务等[^3]。 #### 计算总单词数量 对于给定的一棵完整的字典树来说,可以通过递归方法统计所有具有有效终止状态 (`isEndOfWord=true`) 的叶子数目得到总的唯一单词量。 ```cpp int countWords(TrieNode* node) { if (!node) return 0; int sum = node->isEndOfWord ? 1 : 0; for(int i=0;i<SIZE;++i){ sum +=countWords(node->children[i]); } return sum ; } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值