题意
给一些字符串,问如果使用strcmp函数对这些字符串两两比较,需要进行多少次字符比较。(strcmp函数在题目中已给出,如果字符相等,则还需要将该字符与’\0’进行比较)
题解
链式字典树我自己的叫法,网上大多数题解都把这种字典树叫做左儿子,右兄弟的字典树,但是这种叫法感觉会引起误解。一开始我以为右兄弟是根的兄弟,后来才意识到右兄弟是左儿子的兄弟。
具体效果见下图。
可以看到,这样存储只需要开节点个数大小的数组就可以了,不需要再开一个二维数组,从而节省了大量空间。对于这道题来说,所需的数组空间直接降了1.5个数量级,效果是非常明显的。
至于链式字典树的存储方式,可以用链表,也可以用数组模拟链表。这里我选择用数组模拟链表来避免指针操作。
v=sz++;
c[v]=st[i];
val[v]=0;
son[v]=0;
nex[v]=son[u];
son[u]=v;
上述代码是链式字典树增加节点的过程,由于链式字典树本身的数组无法记录节点对应的字符信息,所以需要增加一个字符记录数组,也就是C。val用来记录节点值,在本题中用来记录访问次数。nex对应上图中的next,son对应上图中的son1。
通过链式字典树解决了存储空间的问题以后,本题就算解决一半了。另一半是统计比较次数。对于普通字符(除了’\0’以外),每次字符比较都会比较两次,调用字符比较的次数就是val[u]值(u表示将字符串插入字典树过程中访问的节点)。如果一直比较到结尾还没有分出大小,那么还会额外针对’\0’比较两次,这时候就需要+cnt[u]。(u代表末尾节点)如果在比较过程中分出了大小,还需要再加1次,因为最后一次不相等的比较没有计算。
最后将所有计算得到的值累加起来就是所求的总比较次数。
代码
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<string>
#include<set>
#include<map>
#include<bitset>
#include<stack>
#include<string>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(a) while(a)
#define MEM(a,b) memset(a,b,sizeof(a))
#define LL long long
#define INF 0x3f3f3f3f
#define MAXN 100010
#define MOD 1000000009
#define EPS 1e-10
using namespace std;
int ch[4000010];
int val[4000010],nex[4000010],son[4000010],cnt[4000010];
char c[4000010];
char st[1010];
int sz;
LL ans;
int getNum(char c) {
if(c>='0'&&c<='9') {
return c-'0';
} else if(c>='a'&&c<='z') {
return c-'a'+10;
} else if(c>='A'&&c<='Z') {
return c-'A'+36;
}
}
void insert() {
int u=0;
int len=strlen(st);
ans+=val[0];
val[0]++;
UP(i,0,len) {
int v;
for(v=son[u];v!=0;v=nex[v]){
if(c[v]==st[i]){
break;
}
}
if(!v) {
v=sz++;
c[v]=st[i];
val[v]=0;
son[v]=0;
nex[v]=son[u];
son[u]=v;
}
u=v;
ans+=val[u]*2;
val[u]++;
}
if(cnt[u]){
ans+=cnt[u];
}
cnt[u]++;
}
int main() {
int n;
int ks=1;
W(~scanf("%d",&n)) {
if(n==0)
break;
MEM(ch,0);
MEM(val,0);
MEM(nex,0);
MEM(son,0);
MEM(cnt,0);
MEM(c,0);
ans=0;
sz=1;
UP(i,0,n) {
scanf("%s",st);
insert();
}
printf("Case %d: %lld\n",ks++,ans);
}
}
/*
3
abc
abc
abc
*/