链接:https://ac.nowcoder.com/acm/problem/15160
来源:牛客网
时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
小H在一片遗迹中发现了一种古老而神秘的文字,这种文字也由26种字母组成,小H用小写字母来代替它们。遗迹里总共有N句话,由于年代久远,每句话都至少有一处无法辨识,用#表示,缺失的可能是任意长度(也包括0)的任意字符串。
小H发现这些话非常相似,现在小H想知道,有多少对句子可能是相同的
注意:(x,x)这样的句子对不计入答案,(x,y),(y,x)视为同一个句子对(详见样例)
输入描述:
第1行,一个整数N 第2~N+1行,每行一个字符串表示一句话 2≤N≤500,000,所有字符串的总长不超过1,000,000
输出描述:
一行,一个整数,表示你给出的答案
示例1
输入
2 a#a #
输出
1
备注:
#表示任意长度的任意字符串
题意:给n个字符串,由小写字母组成,其中有#,代表的意思可以换为任意串,然后询问有多少对字符串是一样的,也就是把字符串中的#号换成字符串之后,该字符串与其他字符串相同就算为一对,顺序无关
题解:可以建两棵tire树,一棵是字符串开头建立,遇到#号就跳出,另外一棵是从后面往前面走,同一个字符串是在同一时间建建立的,这样就可以将从后面建立的和前面建立的联系在一起,然后先拓扑排序下从前面开始建立的,然后根据拓扑序,利用树状数组就可以快速得出答案,其中无非就是求,对于当前遍历到的顺序建树的结点,找他前缀的个数和他满足的后缀的个数,看代码很容易就能想到
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6+9;
char s[maxn];
int id(char s){
return s-'a';
}
struct tire{
int cnt;
int _next[maxn][26];
int insert(char s[]){
int p=0;
for(int i=0;s[i];i++){
if(s[i]=='#')break;
if(!_next[p][id(s[i])]){
_next[p][id(s[i])]=++cnt;
}
p=_next[p][id(s[i])];
}
return p;
}
}T1,T2;
struct BIT{
int bit[maxn];
int n=1e6;
void add(int x,int y){
while(x<=n){
bit[x]+=y;
x+=(x&-x);
}
}
ll query(int x){
ll ans=0;
while(x){
ans+=bit[x];
x-=(x&-x);
}
return ans;
}
}T3,T4;
vector<int>G[maxn];
int dfn;
int be[maxn],en[maxn];
void dfs1(int u){
be[u]=++dfn;
for(int i=0;i<26;i++){
if(T1._next[u][i]){
dfs1(T1._next[u][i]);
}
}
en[u]=dfn;
}
ll ans=0;
void dfs2(int u){
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
ans+=T3.query(be[v]);
T3.add(be[v],1);
T3.add(en[v]+1,-1);
ans+=T4.query(en[v])-T4.query(be[v]);
T4.add(be[v],1);
}
for(int i=0;i<26;i++){
if(T2._next[u][i]){
dfs2(T2._next[u][i]);
}
}
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
T3.add(be[v],-1);
T3.add(en[v]+1,1);
T4.add(be[v],-1);
}
}
int main(){
int n;scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%s",s);
int len=strlen(s);
int x=T1.insert(s);
reverse(s,s+len);
int y=T2.insert(s);
G[y].push_back(x);
}
dfs1(0);
dfs2(0);
printf("%lld\n",ans);
return 0;
}