题目链接: http://poj.org/problem?id=4025
题目大意: 给定n个串,问这些串中某个串的前缀和某个串的后缀组合或者单独某个串能组合成的不同字符串总数,字符串可以重复,n <= 1万,单串长度<=40.
解题思路: 下午不小心打开湖南大学的oj,不小心看到有比赛,不小心地就去做了,不小心地没把这题A掉,赛后不小心地A掉了这题。
这题恨容易想到建两棵字典树或者两个ac自动机,一棵表示前缀,一棵表示后缀,然后利用者两个字典树进行计数。难点在于如何判重,就是去掉重复的部分。现在假设某前缀是xxxxa,某后缀是axxxx,这样就很容易出现重复计算的情况,因为两个串有重叠部分,我们在计算前缀xxxx就组成xxxxaxxxx,而用xxxxa的时候又组成xxxxaxxxx,冗余了。
那我们该怎么去掉重复部分呢?前缀中最后一个字母如果是a,那么后缀中所有以a开头的都会重复计算,我们要把这部分去掉,做法是统计后缀字典树中以a结束的串。注意,这里还要特判一种情况,如果前缀是a,后缀是axxx,这时候不会出现重复计算的情况,因为a之前是空串不曾计算过。
这样做要分两步:第一步用反串建立字典树,这样字典树上和根节点连起来的一条链就是一个后缀,然后统计以每个字母结尾的结点个数以及以每个字母为串结束节点的个数。第二步用正串建立字典树,然后统计不与当前字母为结尾的后缀个数,再加上以当前节点为串结束节点的数目。
正着做显然比较麻烦,我们反着想,总数减去重复的个数就是不同字符串的个数。总数是两个字典树节点数之积,重复的个数就是除去字典树上第一个排字母即正串和反串的第一个字母的所有相同字母出现次数乘积。
最后还需要特判字符串为单字母的时候,单字母无法由一个前缀和后缀组成,很特别。有几个不同单字母加到答案中去就好。
测试数据:
Input:
#include <stdio.h> #include <string.h> #define MAX 410000 #define int64 __int64 struct node { node *next[26]; }tree[MAX],*root; char str[11000][41]; int n,ptr,cnt1,cnt2; int one[30],num1[30],num2[30]; node *CreateNode() { for (int i = 0; i < 26; ++i) tree[ptr].next[i] = NULL; return &tree[ptr++]; } void Initial() { ptr = cnt1 = cnt2 = 0; root = CreateNode(); memset(one,0,sizeof(one)); memset(num1,0,sizeof(num1)); memset(num2,0,sizeof(num2)); } void Insert(char *str,int *num,int &cnt) { node *p = root; for (int i = 0; str[i]; ++i) { int k = str[i] - 'a'; if (p->next[k] == NULL) { cnt++; if (i) num[k]++;//如果某个节点有前继节点,那么就要记录,这就有可能和后缀的某个部分重叠导致重复计算 p->next[k] = CreateNode(); } p = p->next[k]; } } int main() { int i,j,k; while (scanf("%d",&n) != EOF) { Initial(); for (i = 1; i <= n; ++i) { scanf("%s",str[i]); if (strlen(str[i]) == 1) one[str[i][0]-'a'] = 1; Insert(str[i],num1,cnt1); } ptr = 0,root = CreateNode(); for (i = 1; i <= n; ++i) { strrev(str[i]); Insert(str[i],num2,cnt2); } int64 ans = (int64) cnt1 * cnt2; for (i = 0; i < 26; ++i) { if (one[i]) ans++; ans -= (int64)num1[i] * num2[i]; } printf("%I64d\n",ans); } }
本文ZeroClock原创,但可以转载,因为我们是兄弟。