【洛谷】P2580 於是他錯誤的點名開始了

【洛谷】P2580 於是他錯誤的點名開始了


0.總結

Get to the points firstly, the article comes from LawsonAbs!
  • trie 樹
  • 字符串處理

1.trie 樹

在說本題之前,先說說什麼是 trie 樹。認真說,這個知識點的代碼我看了好久都沒能理解,但是它的思想很好理解,有點兒多叉樹的意思,就是能夠公用的節點就公用,否則就另起爐竈,重新搞個新節點。下面細談。
trie 樹分建立和查找兩種情況,先說建立 trie

1.1 建 trie

  • 搞一個原始節點p,將其下標標爲0,即p=0;【這部分可以看成是初始化的賦值】
  • 依次處理每個插入的字符串。遍歷其中的每個字符c,如果從當前節點p有到字符c的路徑,那麼就沿着其往下走,並處理下一個字符。對應代碼: p = trie[p][cNum] 這裏的 cNumc-'a' 得到的值
  • 如果當前節點p沒有到字符c的路徑,那麼就得新建一個節點,並且把節點總數加1。

代碼中涉及到的數組以及定義說明如下:

char name[maxN];//姓名
int trie[maxN][NUM];// 第一維的大小怎麼決定? => 【第一維的大小就是整個trie 樹中的節點數!!!】
int End[maxN];//End[i]表示第i個節點是一個字符串的結束標記
int tot = 0,p = 0; //tot表示節點個數;p 表示開始節點【p的含義是什麼? =》 當前正在判斷的節點序號?】
int cNum;//char型字符串中單個字符-'a'得到的值【是個數字】

建立 trie 樹對應代碼就是:

tot++; 
trie[p][cNum]=tot;

代碼如下:

//使用trie 結構處理這個name
void build(){
    p = 0;//重置
    for(int i = 0;i<strlen(name);i++){//依次遍歷每個字符
        cNum = name[i]-'a';
        if(!trie[p][cNum]){//說明爲空 -> 需要新增一個節點
            tot++;//節點數加一
            trie[p][cNum] = tot; //指向這個樹中新的節點
        }
        p = trie[p][cNum]; // 無論什麼時候都得更新p的值
    }
    End[p] = 1;//處理完了所有的字符,這個時候在p處爲一個結束標記
    return ;
}

1.2 查 trie

同建立 trie 樹一樣,也是需要依次處理字符串中的每個字符,然後沿着字符往下尋找,如果找到了,則說明字符串出現過,否則說明沒有出現過。代碼如下:

void search(char test[]){
    p = 0;//重置爲0
    for(int i = 0;i<strlen(test);i++){
        cNum = test[i] - 'a';
        if(trie[p][cNum])
        	p = trie[p][cNum];        
        else{
            printf("WRONG\n");
            return;
        }
    }
    if(End[p]){
        if(!vis[p])
            printf("OK\n"), vis[p]++;
        else printf("REPEAT\n");
    }
    else printf("WRONG\n");
}

2.題意

給出n個人的名字,然後再給出m個名字,判斷這m個名字是第一次輸入則輸出 OK,如果不是第一次輸入,則輸出 REPEAT ;如果這個名字沒有出現在n個人的名字之中,則輸出WRONG

3.分析

做這道題主要有兩種思路:

  • 使用map 函數,但是好像對於這麼大的數據行不通;
  • 使用上面 介紹的 trie

4.代碼

下面給出這道題的ac 代碼:

// Created by lawson on 20-6-19.
/*
 * 01.在trie樹中,即使是相同的字符可能其p值都不一定相同,原因是:p值的取決於是緊跟在什麼後的字符
 */
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxN = 5e6+5;//最長的名字長度,字符數
const int NUM = 30;//小寫字母的個數

char name[maxN];//姓名
int trie[maxN][NUM]; // 第一維的大小怎麼決定?
int End[maxN],vis[maxN];//結束標記
int tot = 0,p = 0; //tot表示節點個數;p 表示開始節點【p的含義是什麼? =》 當前正在判斷的節點序號?】
int cNum;//char 對應的字符-'a'得到的值

//使用trie 結構處理這個name
void build(){
    p = 0;//重置
    for(int i = 0;i<strlen(name);i++){//依次遍歷每個字符
        cNum = name[i]-'a';
        if(!trie[p][cNum]){//說明爲空 -> 需要新增一個節點
            tot++;
            trie[p][cNum] = tot;
        }
        p = trie[p][cNum]; // 無論什麼時候都得更新p的值
    }
    End[p] = 1;//這個時候在p處爲一個結束標記
    return ;
}


void search(char test[]){
    p = 0;//重置爲0
    for(int i = 0;i<strlen(test);i++){
        cNum = test[i] - 'a';
        if(trie[p][cNum]){
            p = trie[p][cNum];
        }
        else{
            printf("WRONG\n");
            return;
        }
    }
    if(End[p]){
        if(!vis[p])
            printf("OK\n"), vis[p]++;
        else
            printf("REPEAT\n");
    }
    else
        printf("WRONG\n");

}

int main(){
    int n,m;
    scanf("%d",&n);
    for(int i = 1;i<=n;i++){
        scanf("%s",name);
        build(); //處理這個name 信息
    }
    scanf("%d",&m);
    char test[maxN];//點名
    for(int i = 1;i<=m;i++){
        scanf("%s",test);
        search(test);//去trie樹中判斷這個test是否已經存在了
    }
    return 0;
}

5.測試用例

10
wuzuofan
luozhezheng
wangleping
chengjunyang
xudemin
liuyunzhe
wuyaoxuan
gaoqiuyang
huangyeqi
dinghaipeng
10
wangleping
wangleping
wuyaoxuan
chenyande
wuyaoxuan
gaoqiuyang
huangyeqi
dinghaipeng
wuyaoxuan
chengjunyang
OK
REPEAT
OK
WRONG
REPEAT
OK
OK
OK
REPEAT
OK
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章