折腾了两个多小时终于把这个题给过了。
一开始想到思路直接写了个多叉的tire,结果超时了。感觉是不能再小的算法了,后来发现只memset就要很长时间,于是改成每次开辟新节点的时候再memset,还加了读入输出外挂,结果还是超时。后来上网题解上说要用左儿子右兄弟表示法来做,然后写了一个,1.7sAC了。
多叉的写法,由于它直接利用下标表示对应的字符,所以对空间浪费比较严重,但是要找到某个结点的孩子结点是O(1)的。
而二叉的写法,是通过每次比较查找来找到对应字符的,所以没有浪费的空间,但是比较费时。
多叉和二叉的写法,就好像是邻接矩阵和邻接表似的。
这个题多叉写法会超时,大概是数组开的过大,每次memset超时了(想起之前写的几个tire的代码,也都很慢,可能是这个原因)。
用左儿子右兄弟的写法,原来每个case都把全部数组memset一遍,结果跑了1.8s。后来改掉了这块,改成每次建树的时候清空每个结点,变成了0.8s。
这个题的思路,每次建树的时候就考虑每个结点字符出现的次数,与当前结点之前出现次数的比较,这里比较两次,跟父亲结点的比较,这里要去掉父亲结点中当前结点的那块,只比较一次。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
#define ll long long
#define INF 200000000
#define MOD 20071027
#define MAXN 4000*1005
using namespace std;
ll ans;
struct Tire
{
int child[MAXN],next[MAXN],val[MAXN];
char str[MAXN];
int sz;
void Init()
{
sz=1;
child[0]=0;
next[0]=0;
val[0]=0;
}
void Insert(char *word)
{
int u=0,v;
val[u]++;
for(int i=0; word[i]; ++i)
{
bool ok=false;
for(v=child[u]; v; v=next[v])
{
if(str[v]==word[i])
{
ok=true;
break;
}
}
if(!ok)
{
str[sz]=word[i];
val[sz]=0;
child[sz]=0;
v=sz++;
next[v]=child[u];
child[u]=v;
}
ans+=val[v]*2;
val[v]++;
ans+=val[u]-val[v];
u=v;
}
}
};
Tire tree;
char word[1005];
int main()
{
int n,kase=0;
while(scanf("%d",&n)!=EOF&&n)
{
getchar();
ans=0;
tree.Init();
for(int i=0; i<n; ++i)
{
int j=0;
while(word[j]=getchar())
{
if(word[j]=='\n') break;
j++;
}
word[j]='#';
word[j+1]=0;
tree.Insert(word);
}
printf("Case %d: %lld\n",++kase,ans);
}
return 0;
}