竞赛题目讲解-【Rocky Mountain 2004】最短前缀

【Rocky Mountain 2004】最短前缀


Description
一个字符串的前缀是从该字符串的第一个字符起始的一个子串。例如 “carbon”的字串是: “c”, “ca”, “car”, “carb”, “carbo”, 和 “carbon”。注意到这里我们不认为空串是字串, 但是每个非空串是它自身的字串. 我们现在希望能用前缀来缩略的表示单词。例如, “carbohydrate” 通常用”carb”来缩略表示. 现在给你一组单词, 要求你找到唯一标识每个单词的最短前缀
在下面的例子中,”carbohydrate” 能被缩略成”carboh”, 但是不能被缩略成”carbo” (或其余更短的前缀) 因为已经有一个单词用”carbo”开始
一个精确匹配会覆盖一个前缀匹配,例如,前缀”car”精确匹配单词”car”. 因此 “car” 是 “car”的缩略语是没有二义性的 , “car”不会被当成”carriage”或者任何在列表中以”car”开始的单词.

Input
输入包括至少2行,至多1000行. 每行包括一个以小写字母组成的单词,单词长度至少是1,至多是20.
Output
输出的行数与输入的行数相同。每行输出由相应行输入的单词开始,后面跟着一个空格接下来是相应单词的没有二义性的最短前缀标识符。

Sample Input

carbohydrate
cart
carburetor
caramel
caribou
carbonic
cartilage
carbon
carriage
carton
car
carbonate

Sample Output

carbohydrate carboh
cart cart
carburetor carbu
caramel cara
caribou cari
carbonic carboni
cartilage carti
carbon carbon
carriage carr
carton carto
car car
carbonate carbona

题目解析
首先我们要决定使用的变量类型——由于输入全部是字符串,我们可以用string储存(也可以用char数组)。string的方便主要是它的函数特别多,而且这道题有一个特别的输入——它并没有给出输入字符串的数据量!这样就特别麻烦,虽然scanf()是有返回值的,一般来说可以用 Ctrl+Z 来结束输入,但是“%s”与其他任何的占位符都不同,它会将 Ctrl+Z (输入到命令提示符中是 “^Z”)辨认为一个字符串而不结束输入。因此作者使用string,用cin输入,cin也是有返回值的,而它就不会把 Ctrl+Z 辨认为字符串。再分析,数据具有很强的对应性,因此作者推荐使用结构体,来将原串与答案相对应。
然后我们用for依次操作每一个字符串,那么循环多少次呢?题目给出:

一个精确匹配会覆盖一个前缀匹配

于是我们假设最坏情况——所有字符串的长度都相同且只有最后一个字母不同,那么就需要查找到最后一个字母,也就是循环字符串的长度次,由此推导出至少应该循环最长字符串的长度次。接下来开始查找。取前缀我们可以视为取以原串开头的子串,因此我们枚举子串的长度(i)。用string的子函数substr(0,i),来取出前缀,判断是否与其他前缀重复。判断重复也是一个考点——用map(映射),可以定义一个映射为map<string,int>,这样可以定义一个以string类型为下标的int数组,表示每一个前缀的出现次数。再次遍历所有字符串,若找到的前缀在映射中仅出现一次,则保存答案。为了避免重复计算,我们可以在结构体里定义一个bool变量,表示该组数据是否已经匹配完毕
最后进行一个优化,在循环中判断是否所有的字符串都匹配完毕,若是,则退出。


题外话
这道题OpenJudge把它放在贪心算法里面…呵呵…然而没用贪心算法,更像是数据结构。然后实话实说,作者做完这道题的第一个收获是——不要看到前缀就想英语。~TAT~


程序样例

/*Lucky_Glass*/
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
struct String
{
    string s,ans;
    bool finish;
    String() {finish=false;s=ans="\0";}
}S[1005];
int main()
{
    int ls=0,mlen=0;
    while(cin>>S[ls].s)
        mlen=max(mlen,(int)S[ls].s.length()),ls++;
    for(int i=1;i<=mlen;i++)
    {
        bool G=true;
        map<string,int> F;
        for(int j=0;j<ls;j++)
            if(!S[j].finish)
                G=false,F[S[j].s.substr(0,i)]++;
        for(int j=0;j<ls;j++)
            if(!S[j].finish && F[S[j].s.substr(0,i)]==1)
            {
                S[j].ans=S[j].s.substr(0,i);
                S[j].finish=true;
            }
        if(G) break;
    }
    for(int i=0;i<ls;i++)
        cout<<S[i].s<<" "<<S[i].ans<<endl;
    return 0;
}

The End

Thanks for reading

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值