CodeForces 858D Polycarp's phone book(Trie)

本文介绍了一种解决电话号码快速检索的问题,通过构建字典树并采用巧妙的时间戳方法,实现了对每个电话号码找到其最短且唯一的识别码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Polycarp's phone book

time limit per test:4 seconds

memory limit per test:256 megabytes

input:standard input

output:standard output

There are n phone numbers in Polycarp's contacts on his phone. Each number is a 9-digit integer, starting with a digit different from0. 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: 123456789, 100000000 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 from1 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 thei-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



        大致题意,给出很多个号码,然后要你对每一个号码找出一个长度最短的识别码,即长度最短的同时,只有那一个电话包含这个识别码。

        这里队友机智无比,直接想到用字典树搞。由于号码最多只有10位,所以对于每一个号码,在字典树里面分别加入第0位~最后一位,第1位~最后一位,第2位~最后一位……这样解决了字典树只能一定要从头开始的缺陷。如此一来对于一个号码的任意一个小段,我们都可以对应在字典树中找到一个从根开始的路径与之对应。那么我们要做的就是统计一直到任意小段的最后一位的节点,有多少个这样的子串包含这个小段。如果只有一个包含这个小段,那么这个小段可以作为特征码。对于这个小段,由于长度较短,我们直接进行枚举即可,logN判断。

        这样子写了之后,发现样例过不了……我们观察第一个样例的第二个号码1000000000,注意到我们加入字典树的时候,加入了很多个只含有0的子串,那么在我们判断的时候,对于000这个合法的特征码,在字典树中查询的时候出来会显示多个匹配的串。实际上,这些只含有0的串都是统一号码提供的,并不应该被统计进去。那么就有一个新的问题,如何判定哪些要统计。这时,我有机智的脑补,直接加一个时间戳就行了,对于每一个点,我都加一个时间戳,表示该点是被哪一个号码更新的,如果是统一号码,那么在统计size的时候就不把这个统计进去,否则统计。巧妙地解决了这个问题。具体见代码:

#include <bits/stdc++.h>
#define N 100010
using namespace std;

struct Trie
{
    struct node{int size,ch[10],t;}T[N<<5];
    int tot,root;

    void init()
    {
        tot=root=1;
        memset(T,0,sizeof(T));
    }

    void ins(char* x,int t)					//t为时间戳
    {
        int o=root;
        if (T[o].t!=t){T[o].size++;T[o].t=t;}			//如果时间戳不相同则增加,否则不
        for(int k=0;k<strlen(x);k++)
        {
            int c=x[k]-48;
            if(!T[o].ch[c]) T[o].ch[c]=++tot;
            o=T[o].ch[c];
            if (T[o].t!=t){T[o].size++;T[o].t=t;}
        }
    }

    int query(char* x)
    {
        int o=root;
        for(int k=0;k<strlen(x);k++)
        {
            int c=x[k]-48;
            o=T[o].ch[c];
        }
        return T[o].size;
    }
} Trie;

char str[N][10],s[10];
int n;

int main()
{
    scanf("%d\n",&n);
    for(int i=1;i<=n;i++)
        {
            scanf("%s",str[i]);
            for(int j=0;j<strlen(str[i]);j++)
            {
                strcpy(s,str[i]+j);					//把每一位到最后的子串加到字典树中
                Trie.ins(s,i);
            }
        }
    for(int i=1;i<=n;i++)
    {
        bool flag=0;
        for(int len=1;len<=strlen(str[i]);len++)
        {
            for(int b=0;b+len-1<strlen(str[i]);b++)
            {
                memset(s,0,sizeof(s));
                for(int j=0;j<len;j++)
                    s[j]=str[i][j+b];						//暴力枚举特征码,从短的开始枚举
                if (Trie.query(s)==1) {flag=1;break;}			//如果只出现一次,那么合法,直接输出
            }
            if (flag) {printf("%s\n",s); break;}
        }
    }
    return 0;
}

### 关于 Codeforces 上 'Neo's Escape' 的解决方案 在解决 Codeforces 平台上的 `'Neo's Escape'` 问题时,通常需要考虑图论中的最短路径算法以及动态规划的应用。以下是对此类问题可能涉及的核心概念和技术的分析: #### 图论与最短路径 该问题可以被建模为在一个加权有向图中寻找从起点到终点的最优路径。Dijkstra 算法是一种常用的方法来计算单源最短路径[^1]。如果边权重均为正数,则 Dijkstra 是一种高效的选择。 ```python import heapq def dijkstra(graph, start): distances = {node: float('inf') for node in graph} distances[start] = 0 priority_queue = [(0, start)] while priority_queue: current_distance, current_node = heapq.heappop(priority_queue) if current_distance > distances[current_node]: continue for neighbor, weight in graph[current_node].items(): distance = current_distance + weight if distance < distances[neighbor]: distances[neighbor] = distance heapq.heappush(priority_queue, (distance, neighbor)) return distances ``` #### 动态规划优化 对于某些变体问题,除了简单的最短路径外,还需要引入状态转移方程来进行进一步优化。例如,在存在多种属性约束的情况下(如时间、能量),可以通过定义多维数组 `dp[i][j]` 来表示到达节点 i 使用 j 单位资源所需的最小代价[^2]。 #### 讨论与实现细节 社区内的讨论往往围绕如何处理特殊边界条件展开,比如是否存在负环路或者超大数据集下的性能调优等问题。此外,部分参赛者会分享他们关于数据结构选取的经验教训,例如优先队列 vs. 堆栈的不同适用场景[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值