简介:
给出c++中strcmp函数:
给出n个字符串,问两两cmp,==操作执行的次数
分析:
我们不考虑暴力的两两枚举
首先把所有的串扔到一棵trie树上
看题目给出的例子,than和that的前三个字符相同,比较次数为7
实际上:
当字符串a和字符串b不完全相等时,比较次数为len(LCP)*2+1
否则为2*len(a)
因为这道题的结点数较多,我们在存储的时候用的是左儿子右兄弟法
那问题就一定会在trie树的基础上解决了,具体计数方式,会在下面详细说明
下面就是喜闻乐见的代码详解了:
变量介绍:
主要过程只有两个,insert和count
insert
题目有明确要求:Of course it assumes that last character of a string is a null(‘\0’) character.
所以我们需要把末尾的’\0’也算入
设当前插入的字符为X
当前节点是pre,我们需要在ta的儿子中找到插入X的位置
看到这个循环,非常像邻接表:
for (now=son[pre];now;now=nxt[now]) //循环now的所有儿子
if (ch[now]==s[i]) {ff=1;break;}
我们如果没有在pre的儿子中找到X,就说明我们需要新建一个结点,接到pre下面
这里维护的信息比较多,实在记不住的话,就顺着变量表都维护一遍
同时,时刻维护tot的值
count
这就牵扯到我们之前说的了:
当字符串a和字符串b不完全相等时,比较次数为len(LCP)*2+1
否则为2*len(a)
First.
当now是叶子结点的时候,now代表的子串都相等,所以属于第二种情况
ans=tot[now] * (tot[now]-1)/2 * deep[now] * 2=tot[now] * (tot[now]-1) * deep[now]
(tot*(tot-1)是因为我们要不重不漏的把子串两两结合)
Second.
当now不是叶子结点的时候,
now的每个分叉之间都要互相组合(分叉本身的组合,通过下一层dfs得到)
所以组合数为:tot[son] * (tot[now]-tot[son])
设sum=Σ[tot[son] * (tot[now]-tot[son])]
ans=sum / 2* (2 * deep[now] + 1)
之后就是进一步的dfs
//这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int M=4000*1000+3;
struct Trie{
int son[M]; //左儿子
int nxt[M]; //右兄弟
char ch[M]; //第i个结点上的字符
int tot[M]; //子树i的叶子数
int cnt; // 结点总数
ll ans;
void init()
{
memset(son,0,sizeof(son));
memset(tot,0,sizeof(tot));
memset(nxt,0,sizeof(nxt));
cnt=0;
}
void insert(char *s)
{
int pre=0,now;
int len=strlen(s);
tot[0]++;
for (int i=0;i<=len;i++) //\0也算入
{
bool ff=0;
for (now=son[pre];now;now=nxt[now]) //循环now的所有儿子
if (ch[now]==s[i]) {ff=1;break;}
if (!ff)
{
now=++cnt;
ch[now]=s[i];
tot[now]=0;
nxt[now]=son[pre]; //把新节点直接连到父节点下面
son[pre]=now; son[now]=0;
}
pre=now;
tot[pre]++;
}
}
void dfs(int now,int dep)
{
if (son[now]==0) //叶结点
ans+=tot[now]*(tot[now]-1)*dep;
else
{
int sum=0;
for (int i=son[now];i;i=nxt[i])
sum+=tot[i]*(tot[now]-tot[i]);
sum/=2;
ans+=(ll)sum*(2*dep+1);
for (int i=son[now];i;i=nxt[i])
dfs(i,dep+1);
}
}
ll count()
{
ans=0;
dfs(0,0);
return ans;
}
};
Trie trie;
const int N=1005;
char s[N];
int n;
int main()
{
int cas=0;
while (scanf("%d",&n)!=EOF&&n)
{
trie.init();
for (int i=1;i<=n;i++)
{
scanf("%s",&s);
trie.insert(s);
}
printf("Case %d: %lld\n",++cas,trie.count());
}
return 0;
}