Wannafly挑战赛10F-小H和遗迹【Trie,树状数组】
正题
题目链接:https://ac.nowcoder.com/acm/contest/72/F
题目大意
\(n\)个字符串,包括小写字母和\(\#\)。其中\(\#\)可以替换为任意字符串。求有多少对字符串可能相同。
保证每个字符串至少有一个\(\#\)。
\(2\leq n\leq 500000,\sum_{i=1}^n |s_i|\leq 10^6\)
解题思路
因为可以替换为任意字符串,所以只需要考虑第一个\(\#\)前和最后一个\(\#\)后的部分。
在仔细考虑一下,这个字符串分成前后的两个部分\(s,t\)。数对\((x,y)\)满足条件当且仅当\(s_x\)是\(s_y\)的前缀,或者\(s_y\)是\(s_x\)的前缀,且\(t_x\)是\(t_y\)的后缀,或者\(t_y\)是\(t_x\)的后缀。
放到两棵\(Trie\)树上就是都有祖先关系就好了,直接跑第一棵上,然后用两个树状数组在第二棵树上维护就好了。
时间复杂度\(O(m\log m)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define lowbit(x) (x&-x)
using namespace std;
const int N=1e6+10;
int n,cnt,pos[N],dfn[N],ed[N];
long long ans;char s[N];
vector<int> G[N];
struct TreeBinary{
int t[N];
void Change(int x,int val){
while(x<=cnt){
t[x]+=val;
x+=lowbit(x);
}
return;
}
int Ask(int x){
int ans=0;
while(x){
ans+=t[x];
x-=lowbit(x);
}
return ans;
}
}Bf,Bs;
struct Trie{
int t[N][26],m=1;
int Insert(char *s,int n){
int x=1;
for(int i=1;i<=n;i++){
if(s[i]=='#')break;
int c=s[i]-'a';
if(!t[x][c])t[x][c]=++m;
x=t[x][c];
}
return x;
}
}Tp,Ts;
void dfs(int x){
dfn[x]=++cnt;
for(int i=0;i<26;i++)
if(Ts.t[x][i])
dfs(Ts.t[x][i]);
ed[x]=cnt;
}
void work(int x){
for(int i=0;i<G[x].size();i++){
int p=G[x][i];ans+=Bf.Ask(dfn[pos[p]]);
Bf.Change(dfn[pos[p]],1);Bf.Change(ed[pos[p]]+1,-1);
ans+=Bs.Ask(ed[pos[p]])-Bs.Ask(dfn[pos[p]]);
Bs.Change(dfn[pos[p]],1);
}
for(int i=0;i<26;i++)
if(Tp.t[x][i])
work(Tp.t[x][i]);
for(int i=0;i<G[x].size();i++){
int p=G[x][i];Bs.Change(dfn[pos[p]],-1);
Bf.Change(dfn[pos[p]],-1);Bf.Change(ed[pos[p]]+1,1);
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
int l=strlen(s+1);
int x=Tp.Insert(s,l);
G[x].push_back(i);
reverse(s+1,s+1+l);
pos[i]=Ts.Insert(s,l);
}
dfs(1);
work(1);
printf("%lld\n",ans);
return 0;
}