[USACO]1.2.3 name that number

4 篇文章 0 订阅
3 篇文章 0 订阅

题目大意

:输入一串数字,根据数字可以导出一大堆名字,要找出所有既能导出,又存在于字典当中的名字。其中2可以代表A,B,C;3代表D,E,F,依此类推,但是字母Q和Z不存在。
键盘分布
其中字典需要单独读入,文件名为dict.txt

首先是正常人的解法

不要被题目骗了
题目叫你通过数字找出所有名字你就通过数字找出所有名字???
那你484撒(对我就是撒

深搜的深度最大为13,需要遍历3^d个搜索树节点。

改变一下策略,反正你要挨个读入字典dict.txt,那还不如反着把字典里头的名字转化成数字代码。
打表:

int map[26]={2,2,2,//ABC
            3,3,3,//DEF
            4,4,4,//GHI
            5,5,5,//JKL
            6,6,6,//MNO
            7,0,7,7,//PQRS,其中Q不存在所以设为0
            8,8,8,9,9,9};

论如何将一个“名字”转化成代码

int transfer(string name){
    int ret=0;
    for(int i=0;i<name.length();i++){
        ret*=10;
        ret+=map[name[i]-'A'];//数据保证大写
    }
    return ret;
}
//main()
FILE *d=fopen("dict.txt","r");
char buffer[30];
while(~fscanf(d,"%s",buffer){
    if(transfer(buffer)==code){
        exist=true;
        cout<<buffer<<endl;
    }
}
if(!exist)cout<<"NONE";

做一个逗逼

我就脑残地建立了两棵字典树+dfs(微笑)虽然时间慢了(每次都要把所有情况算出来)而且空间大了(这不明显么)然而还是过了13组数据(共15组,以wa告终,我错在没有排除Q和Z)我觉得那行吧顺便复习下字典树和dfs,就又写了一遍。(逗逼青年欢乐多)


字典树:

struct node {
    node *child[26];
    bool exist;//记录是否为终结点
    //终结点是我自己起的名字,我并不知道应该叫什么= =
    node() {
        for (int i = 0; i < 26; i++)child[i] = 0;
        exist = 0;
    }
};
class Trie {
    node *root;
public:
    Trie() {
        root = new node;
    }
    void insert(string str) {
        node *scout = root;
        for (int i = 0; i < str.length(); i++) {
            int target = str[i] - 'A';//题目保证全大写
            if (!scout->child[target])scout->child[target] = new node;
            scout = scout->child[target];
        }
        scout->exist = 1;
    }
    friend void compare(Trie&, Trie&);
};

为了题目,我暂时没有写查询函数,因为不需要(废话)
其中compare(Trie &,Trie &) 是用来同时遍历两棵字典树用的。规则如下:

1、对于任意结点,如果a树中存在但b树中不存在,则不访问此节点
2、对于任意节点,如果此节点同时在a、b两棵树中以终结点(从根结点到此节点能够形成一个存在字典中的单词)则输出此节点的单词。

我的trie树节点的特点是,结点本身不存储自己是’a’还是’b’,链接到上一节点的对应位置,这样既可以保证访问速度又可以保证存储空间。

对任意树遍历方法:

void traversal(node *target, string current) {
    for (int i = 0; i < 26; i++)
        if(target->child[i])
            traversal(target->child[i],current+(char)(i+'a'));
    if (target->exist){
        //todo:输出节点信息
        //optional:计数器++
    }
}
/*调用:*/traversal(root,"");

言归正传,现在用这个开始做题
首先实例化两棵字典树all和dict

Trie all,dict;

咱先把那个神奇的数字串dfs一下,把所有可能的名字找出来(逗逼本质暴露的时刻到了)

string key;
void dfsName(int digit, int num, string name) {
    if (digit >= key.length()) {
        all.insert(name);//向其中一棵字典树中插入能打出的所有名字
        return;
    }
    for (int i = 0; i < 3; i++) {
        dfsName(digit + 1, i, name + (char)(i + (key[digit] - '0') * 3 - 6 + 'A'));
        //错就错在这了,这里只适用于'A'到'P'之间的名字。
        //总之我懒得改了
    }
}
//把电话号码读到key里头然后调用:
dfsName(0,0,"");

然后再读取dict.txt…

FILE *dicttxt=fopen("dict.txt","r");
char buffer[30];//13
while(~fscanf(dicttxt,"%s",buffer))dict.insert(buffer);

至此我们有了两棵字典树。接下来我们要做的就是把两颗字典树“重叠”起来,同时遍历,就可以找到所有共有的终结点了。

void compare(node *a, node *b, string current) {
    node *sca = a, *scb = b;
    for (int i = 0; i < 26; i++) {
        if (sca->child[i] && scb->child[i])
            compare(sca->child[i], scb->child[i], current + (char)(i + 'A'));
            //根据遍历策略,我需要走一步,"顺"一个字母。从上到下连起来成为单词
    }
    if (sca->exist&&scb->exist){
            good=true;
            cout << current.c_str() << endl;
        }
}
void compare(Trie &a, Trie &b) {
    compare(a.root, b.root, "");
    //这就是为什么我在trie类中要声明friend void compare (Trie &,Trie &);
}

综上,在这里放一下我ac的代码(成就:放肆的逗逼青年:达成)

#include<iostream>
using namespace std;
struct node {
    node *child[26];
    bool exist;
    node() {
        for (int i = 0; i < 26; i++)child[i] = 0;
        exist = 0;
    }
};
class Trie {
    node *root;
public:
    Trie() {
        root = new node;
    }
    void insert(string str) {
        node *scout = root;
        for (int i = 0; i < str.length(); i++) {
            int target = str[i] - 'A';
            if (!scout->child[target])scout->child[target] = new node;
            scout = scout->child[target];
        }
        scout->exist = 1;
    }
    friend void compare(Trie&, Trie&);
}all, dict;
bool good=0;
void compare(node *a, node *b, string current) {
    node *sca = a, *scb = b;
    for (int i = 0; i < 26; i++) {
        if (sca->child[i] && scb->child[i])
            compare(sca->child[i], scb->child[i], current + (char)(i + 'A'));
    }
    if (sca->exist&&scb->exist){
            good=true;
            cout << current.c_str() << endl;
        }
}
void compare(Trie &a, Trie &b) {
    compare(a.root, b.root, "");
}
string key;
void dfsName(int digit, int num, string name) {
    if (digit >= key.length()) {
        all.insert(name);
        return;
    }
    for (int i = 0; i < 3; i++) {
        char newDigit=(char)(i + (key[digit] - '0') * 3 - 6 + 'A');
        if(newDigit>='Q')newDigit++;
        dfsName(digit + 1, i, name + newDigit);
    }
}
int main() {
    freopen("namenum.in","r",stdin);
    freopen("namenum.out","w",stdout);
    char a[13];
    FILE *diccc=fopen("dict.txt","r");
    while(~fscanf(diccc,"%s",a))dict.insert(a);
    cin >> a;
    key = a;
    dfsName(0, 0, "");
    compare(all, dict);
    if(!good)cout<<"NONE";
}

好的。我觉得没有人会像我这样写这样逗逼的题解。所以我决定继续写下去。

其实,USACO是支持C++11的,也就是说我可以放肆地用STL(standard template library)里的set,不用担心兼容不兼容。所以其产物如下。

#include<set>
using namespace std;
#include<iostream>
set<string> dict;
bool exist=false;
string key;
void dfsName(int digit, int num, string name) {
    if (digit >= key.length()) {
        if(dict.find(name)!=dict.end()){
            exist=true;
            cout<<name<<endl;
        }
        return;
    }
    for (int i = 0; i < 3; i++) {
char newDigit=(char)(i + (key[digit] - '0') * 3 - 6 + 'A');
        if(newDigit>='Q')newDigit++;
        dfsName(digit + 1, i, name+newDigit);}
}
int main(){
    freopen("namenum.in","r",stdin);
    freopen("namenum.out","w",stdout);
    FILE *diccc=fopen("dict.txt","r");
    while(~fscanf(diccc,"%s",a))dict.insert(a);    
    cin>>a;key=a;
    dfsName(0,0,"");
    if(!exist)cout<<"NONE";
}

ac
interesting

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值