BNU OJ 29355 手速为王
题目链接:http://www.bnuoj.com/bnuoj/problem_show.php?pid=29355
与Trie树相关的计数问题。
因为正反各插了一次,所以字典中一共有2N个单词。
val[i]存储了在该位置有相同字母的单词的个数,对于在该位置的每个字母,2N-val[i]代表与其不同的单词个数,所以对于每个单词的每个位置的字母,不同的需要删,相同的不需要删。对于在该位置的所有单词,一共删的次数即为val[i]*( 2*N - val[i] )。
把每个位置都跑一边,最后的式子即为:ans += val[i]*( 2*N - val[i] )
这个方法也是从别人的代码里学来的,我能力有限,说不太清楚,请在式子中自己体会=3=
个人感觉这个计数方法很不错,我自己想想不出来。。。
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 5 #define LL long long int 6 7 const int MAXSIZE = 200100; 8 const int sigma_size = 26; 9 10 struct Trie 11 { 12 int val[MAXSIZE]; 13 int ch[MAXSIZE][sigma_size]; 14 int sz; 15 16 void init() 17 { 18 memset( val, 0, sizeof(val) ); 19 memset( ch[0], 0, sizeof(ch[0]) ); 20 sz = 1; 21 return; 22 } 23 24 inline int idx( char cc ) 25 { 26 return cc - 'a'; 27 } 28 29 void InsertStr( char *s ) 30 { 31 int len = strlen(s); 32 33 int u = 0; 34 for ( int i = 0; i < len; ++i ) 35 { 36 int c = idx( s[i] ); 37 if ( !ch[u][c] ) 38 { 39 memset( ch[sz], 0, sizeof(ch[sz]) ); 40 ch[u][c] = sz++; 41 } 42 u = ch[u][c]; 43 ++val[u]; 44 } 45 46 u = 0; 47 for ( int i = len - 1; i >= 0; --i ) 48 { 49 int c = idx( s[i] ); 50 if ( !ch[u][c] ) 51 { 52 memset( ch[sz], 0, sizeof(ch[sz]) ); 53 ch[u][c] = sz++; 54 } 55 u = ch[u][c]; 56 ++val[u]; 57 } 58 return; 59 } 60 }; 61 62 Trie tr; 63 char str[MAXSIZE]; 64 65 int main() 66 { 67 int T; 68 scanf( "%d", &T ); 69 while ( T-- ) 70 { 71 tr.init(); 72 73 int N; 74 scanf( "%d", &N ); 75 for ( int i = 0; i < N; ++i ) 76 { 77 scanf( "%s", str ); 78 tr.InsertStr( str ); 79 } 80 81 LL ans = 0; 82 for ( int i = 1; i <= tr.sz; ++i ) 83 ans += tr.val[i] * ( N + N - tr.val[i] ); 84 85 printf( "%lld\n", ans ); 86 } 87 return 0; 88 }
我能说这题的精华就是这一句ans += tr.val[i] * ( N + N - tr.val[i] )么……TAT……只要能想清楚这点,这题就很简单了。
最后想吐槽下自己:刚在赛场上看见这题的时候眼前一亮——哦哦哦Trieeeeee~~~终于找到一个我会的数据结构……OTL
然后一想计数问题就傻了,半天没搞明白。一直纠结于前缀有重叠,计数如何才能不重不漏云云……
单会数据结构,思路转化不来又有什么用……说到底还是跟不会一样……