题目大意:给你若干字符串,让你找出这些字符串中由其他两个字符串连接成的字符串。
分析:我们很容易想到的是先把这些字符串存放到tire树中,然后分别查询每两个字符串连接构成的新字符串是否在树中,想法很美好,但这样的时间复杂度是0(n^2)的,而n是50000的,果断TLE了。后来我换了一种思路,和合并操作等价的操作就是拆分:我们遍历这n个字符串,把每个字符串可拆分的字符串找出来,分别判断他们是否在tire树中即可,这样复杂度就变为了O(n*len)其中len为字符串的最大长度,这个长度很小的(按AC的代码来看,15就足矣)。
实现代码如下:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
#define son_num 30 //字符串中包含的字符个数
#define maxn 15 //单词的最大长度
struct tire
{
int num; //纪录到达该节点的字符串的个数,即相同前缀数
bool terminal; //如果terminal=true,表示当前点为该字符串最后一个字符
struct tire *next[son_num]; //纪录下一个节点
};
tire *root;
char dic[50005][maxn];
tire *init() //创建新节点
{
tire *p=(tire *)malloc(sizeof(tire));
for(int i=0;i<son_num;i++)
p->next[i]=NULL;
p->terminal=false;
p->num=0;
return p;
}
void insert(tire *root,char str[]) //插入操作
{
int i=0;
tire *p=root;
while(str[i]!='\0')
{
if(!p->next[ str[i]-'a' ]) //如果不存在,建立新节点
p->next[ str[i]-'a' ]=init();
p=p->next[ str[i]-'a' ];
p->num++;
i++;
}
p->terminal=true;
}
bool find(tire *p,char str[]) //查找操作
{
int i=0;
while(str[i]!='\0'&&p->next[ str[i]-'a' ])
{
p=p->next[ str[i]-'a' ];
i++;
}
if(str[i]=='\0'&&p->terminal) return true; //查找字符串本身
return false;
}
void del(tire *root) //清空操作
{
for(int i=0;i<son_num;i++)
if(root->next[i]!=NULL)
del(root->next[i]);
free(root);
}
int main()
{
root=init(); //创建根结点
int cnt=0;
while(~scanf("%s",dic[cnt])) //建立字典树
{
insert(root,dic[cnt]);
cnt++;
}
for(int i=0;i<cnt;i++) //拆分字符串并查找
{
int len=strlen(dic[i]);
char tmp1[maxn],tmp2[maxn];
for(int j=1;j<len;j++)
{
strncpy(tmp1,dic[i],j);
tmp1[j]='\0';
strncpy(tmp2,dic[i]+j,len-j);
tmp2[len-j]='\0';
if(find(root,tmp1)&&find(root,tmp2))
{
printf("%s\n",dic[i]);
break;
}
}
}
del(root); //释放字典树占用的空间
return 0;
}