Trie。反思:一直想的是直接把答案算出来,然后就各种wa。其实反着想的话才是正解。。。就是先把所有情况都算出来,然后减去重复的情况。这个样例
3
abac
bc
就是有组成新串有重复情况的样例。对付这种题,很显然我们会想到要建两个Trie,一个存前缀,一个存后缀。然后两个Trie的节点数相乘就是总数了。关键是去重。考虑一下节点数相乘求新串个数的原理(假如没有重复。)我们是在前缀trie上一个字符到根这一段字符串作为新串的前半段,在后缀trie上一个字符到根这一段字符串作为新串的后半段(串为反串)。这样的话,因为两部分都不能为空,我们需要统计每个字母出现的次数。如我前面写的那个样例:
前缀Trie 后缀Trie
这样的话我们取前缀ab,后缀c 为一个串abc。然后前缀 a ,后缀 bc 又组成了相同串 abc。这样的话就是重复的位置了,当然,这也是解决问题关键。为什么会出现这种情况呢?是因为两边都有字母b!导致abc,这种串算了两次。于是结果应该减去这种重复。统计两边相同字母出现次数,然后用总数减去他们的乘积就好了。对于与根节点相连的字母我们是不用统计的,因为我们是两边都取串,这样的话原串对应的串就不会被重复进去,原串只算了一遍,所以不用统计。然后就是如果输入的有长度为 1 的串的话要单独考虑自身串,因为这种串是不会出现在两前面所求的情况中的。
#include <algorithm>
#include <iostream>
#include <sstream>
#include <cstdlib>
#include <climits>
#include <cstring>
#include <cstdio>
#include <string>
#include <vector>
#include <cctype>
#include <queue>
#include <cmath>
#include <set>
#include <map>
#define CLR(a, b) memset(a, b, sizeof(a))
using namespace std;
typedef long long LL;
const int MAX_NODE = 10010 * 40;
const int INF = 0x3f3f3f3f;
const int CHILD_NUM = 26;
const int N = 44;
class Trie
{
private:
int chd[MAX_NODE][CHILD_NUM];
LL cnt[CHILD_NUM];
int ID[128];
int sz;
public:
void Initialize() {
for(int i = 0; i < 26; i ++)
ID[i + 'a'] = i;
}
void Reset() {
CLR(chd[0] , 0);sz = 1;
CLR(cnt, 0);
}
void Insert(char *a)
{
int p = 0;
for ( ; *a ; a ++) {
int c = ID[*a];
if (!chd[p][c]) {
CLR(chd[sz] , 0);
if(p != 0) cnt[c] ++;
chd[p][c] = sz ++;
}
p = chd[p][c];
}
}
LL CNT(int i) { return cnt[i]; }
int SZ() { return sz - 1;}
} Tr1, Tr2;
char a[N], b[N];
int main()
{
//freopen("input.txt", "r", stdin);
int n, len;bool cnt[27];LL ans;
Tr1.Initialize();
Tr2.Initialize();
while(scanf("%d", &n) != EOF)
{
Tr1.Reset();Tr2.Reset();CLR(cnt, 0);
for(int i = 0; i < n; i ++)
{
scanf("%s", a);
len = strlen(a);
if(len == 1) cnt[a[0] - 'a'] = 1;
for(int j = 0; j < len; j ++)
{
b[len - j - 1] = a[j];
}
b[len] = '\0';
Tr1.Insert(a);
Tr2.Insert(b);
}
ans = LL(Tr1.SZ()) * Tr2.SZ();
for(int i = 0; i < 26; i ++)
{
ans -= Tr1.CNT(i) * Tr2.CNT(i);
if(cnt[i]) ans ++;
}
printf("%I64d\n", ans);
}
}