【原】 POJ 3630 Phone List Trie树 解题报告

 

http://poj.org/problem?id=3630

 

方法一:
利用Trie可以很好的解决此类前缀问题。它有一个很大的好处就是可以边插入边判断是否是前缀。
由于号码只由0~9组合,因此Trie的数组分支也为10个,即son[10](就像当英文字典时为son[26])。

一般而言Trie的节点都在需要时被new出来,但这样无疑会浪费太多的时间。因此我们可以根据已知条件先估算出大概需要的节点数,利用数组缓存。
该题已知最多有10,000个号码,每个号码最多10位。假设最坏情况下,每个节点仅对应号码中的一位(实际中一个节点对应着这些号码中的多个位),此时共需10,000*10=100,000个节点。以此为缓存大小开辟数组。

此题的关键在于插入步骤,在插入过程中同时判断是否是前缀。现在假设两种情况,以911和91123为例
1、假设91123已经在Trie中,现在要将911插入。假设此时在插最后一个字符1,这时需要判断该1是否也在Trie中,
    如果在则证明911是某个字符串的前缀,返回false。如果不在则将其插入。
2、假设911已经在Trie中,现在要将91123插入。插入到第二个1时,因为1已经在Trie中,所以判断是不是有以该1为结尾的字符串。如果有则证明存在一个字符串时91123的前缀,返回false.
这里需要注意的是(1)为特殊情况,需要特殊判断,此处才是可以边插边查的关键所在!!!

复杂度:O(n*len),边建Trie边查,可能在插入一部分之后判断出有前缀就不用再插入了

 

方法二:
对所有号码按字符串排序,这样就将有前缀关系的号码排到了一起,顺序比较两两相邻号码即可判断是否存在前缀

复杂度:O(n*logn)+O(n*len)

 

Description

Given a list of phone numbers, determine if it is consistent in the sense that no number is the prefix of another. Let's say the phone catalogue listed these numbers:

  • Emergency 911
  • Alice 97 625 999
  • Bob 91 12 54 26

In this case, it's not possible to call Bob, because the central would direct your call to the emergency line as soon as you had dialled the first three digits of Bob's phone number. So this list would not be consistent.

Input

The first line of input gives a single integer, 1 ≤ t ≤ 40, the number of test cases. Each test case starts with n, the number of phone numbers, on a separate line, 1 ≤ n ≤ 10000. Then follows n lines with one unique phone number on each line. A phone number is a sequence of at most ten digits.

Output

For each test case, output "YES" if the list is consistent, or "NO" otherwise.

Sample Input

2

3

911

97625999

91125426

5

113

12340

123440

12345

98346

Sample Output

NO

YES

 

   1: #include <iostream>
   2: #include <stdio.h>
   3:  
   4: using namespace std ;
   5:  
   6: struct Node
   7: {
   8:     Node():isStr(false){ memset(son, NULL, sizeof(son) ); }
   9:     bool isStr ;    //为true证明有以该节点为尾的字符串
  10:     Node* son[10] ; //数字0~9
  11: };
  12:  
  13: Node buffer[100010] ;  //***重点!!!估计出大概的节点数后用数组缓存,节省了new的时间
  14: //int bufnum = 1 ;
  15:  
  16: class Trie
  17: {
  18: private:
  19:     Node root ;  //***
  20:     int bufnum ; //指示buffer已经用到哪里了
  21: public:
  22:     Trie():root(buffer[0]),bufnum(1){}
  23:     bool InsertAndCheck( char* s ) ;
  24:     //void Clear(){ bufnum=1 ; }
  25: };
  26:  
  27: bool Trie::InsertAndCheck(char *s)
  28: {
  29:     Node *p = &root ;  //***
  30:     int len = strlen(s) ;
  31:     int digit, i ;
  32:  
  33:     for( i=0 ; i<len ; ++i ) //将字符串逐渐插入到Trie中
  34:     {
  35:         digit = s[i]-'0' ;
  36:         if( i==len-1 && p->son[digit]!=NULL ) //特殊情况:要插入的词是已有词前缀的情况。已有91123,插911
  37:             return false ;                    //如果将所有词按长短排序则不需要这步。这步是可以边插边查的关键!!!
  38:  
  39:         if( p->son[digit] == NULL )   //如果该字符没出现,则插入,这里用buffer代替new
  40:         {
  41:             p->son[digit] = &buffer[bufnum] ;
  42:             buffer[bufnum].isStr = false ;
  43:             memset( buffer[bufnum].son, NULL, sizeof(buffer[bufnum].son) ) ;
  44:             ++bufnum ;
  45:         }
  46:         else  //该字符已在Trie中
  47:         {
  48:             if( p->son[digit]->isStr == true )  //判断是否有以此字符为结尾的字符串
  49:                 return false ;                  //如果有则已有词是插入词的前缀
  50:         }
  51:         p = p->son[digit] ;  //p指向下一层
  52:     }
  53:  
  54:     p->isStr = true ;  //已插入一个单词
  55:     return true ;
  56: }
  57:  
  58: void run3630()
  59: {
  60:     int num, n, i ;
  61:     char s[11] ;
  62:     bool flag ;
  63:  
  64:     scanf( "%d", &num ) ;
  65:     while( num-- )
  66:     {
  67:         Trie trie ;
  68:         //trie.Clear() ;
  69:         flag = true ;
  70:         scanf( "%d", &n ) ;
  71:         while( n-- )
  72:         {
  73:             scanf( "%s", s ) ;
  74:             flag = trie.InsertAndCheck( s ) ;
  75:             if( !flag )
  76:                 break ;
  77:         }
  78:         while( n-- > 0 )  //如果在插入的过程中得到flag=false,那么后面的输入就没必要在插入并判断,因此break出来
  79:             scanf( "%d", s ) ;  //之后再将其余的输入过掉
  80:  
  81:         if( flag )
  82:             printf( "YES\n" ) ;
  83:         else
  84:             printf( "NO\n" ) ;
  85:     }
  86: }
  87:  
  88: //************
  89:  
  90: char ss[10010][11] ;
  91:  
  92: int cmp( const void* a, const void* b )  //一定返回int,不能是bool
  93: {
  94:     return strcmp( (char*)a, (char*)b ) ;
  95: }
  96:  
  97: void run3630_1( )
  98: {
  99:     int num, n, i, j, k, len1, len2 ;
 100:     bool flag ;
 101:  
 102:     scanf( "%d", &num ) ;
 103:     while( num-- )
 104:     {
 105:         flag = true ;
 106:         scanf( "%d", &n ) ;
 107:         for( i=0 ; i<n ; ++i )
 108:             scanf( "%s", ss[i] ) ;
 109:         qsort( ss, n, sizeof(char)*11, cmp ) ;  //按字符串顺序排序,前缀一定在包含它的字符串之前且紧挨
 110:         for( i=0 ; i<n-1 ; ++i )
 111:         {
 112:             k = 0 ;
 113:             j = i+1 ;
 114:             len1 = strlen( ss[i] ) ;
 115:             len2 = strlen( ss[j] ) ;
 116:             if( len1 < len2 )
 117:             {
 118:                 while( ss[i][k]!='\0' && ss[i][k]==ss[j][k] )
 119:                     ++k ;
 120:                 if( ss[i][k]=='\0' )  //存在前缀
 121:                 {
 122:                     flag = false ;
 123:                     break ;
 124:                 }
 125:             }
 126:         }
 127:  
 128:         if( flag )
 129:             printf( "YES\n" ) ;
 130:         else
 131:             printf( "NO\n" ) ;
 132:     }
 133: }
posted @ 2010-11-08 19:59  Allen Sun  阅读(414)  评论(0编辑  收藏  举报