鏈接: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;
}