版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/83216513
传送门
解析:
神TM,OJ上又双叒叕卡栈空间,没办法手动开栈。。。
思路:
首先要发现一个性质,就是对于一个字符串,除了第一个#之前的前缀和最后一个#之后的后缀,其余部分是没有任何性质决定它的形态的。。。
所以我们建立两颗 树,直接将前缀和后缀插入到 树里面,同时维护一下前缀 上面的结尾位置在后缀 上对应的结尾位置。
两个字符串能够相互包含,当且仅当两个字符串的前缀存在包含关系且后缀存在包含关系。
说人话就是其中一个的前缀是另一个前缀的前缀,其中一个的后缀是其中一个后缀的后缀。
那么这种包含关系映射到 树上就是,其中一个的结束标记在另一个结束标记到根的路径上。
那么直接在前缀 上面 ,在后缀 上面进行单点加,子树求和和链求和就行了,直接 序加树状数组维护就行了。
注意在更新的时候不要在子树加的树状数组里面把当前子树的根节点再加一次,因为本身在链求和的时候已经统计过了。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define cs const
cs int N=1000056;
int son[N<<1][26],total=1;
int in[N<<1],out[N<<1],tot;
vector<int> to[N<<1];
void dfs1(int u){
in[u]=++tot;
for(int re i=0;i<26;++i)
if(son[u][i])dfs1(son[u][i]);
out[u]=tot;
}
#define lowbit(x) (x&(-x))
ll t1[N],t2[N];
inline ll query(ll *const a,int pos){
ll res=0;
for(;pos;pos-=lowbit(pos))res+=a[pos];
return res;
}
inline void add(ll *const a,int pos,int val){
for(;pos<=tot;pos+=lowbit(pos))a[pos]+=val;
}
ll ans=0;
void dfs(int u){
for(int re i=0;i<to[u].size();++i){
int v=to[u][i];
ans+=query(t1,out[v])-query(t1,in[v]-1);
ans+=query(t2,in[v]);
add(t1,in[v],1);
add(t2,in[v]+1,1);
add(t2,out[v]+1,-1);
}
for(int re i=0;i<26;++i)
if(son[u][i])dfs(son[u][i]);
for(int re i=0;i<to[u].size();++i){
int v=to[u][i];
add(t1,in[v],-1);
add(t2,in[v]+1,-1);
add(t2,out[v]+1,1);
}
}
int n;
char c[N];
signed main(){
ll size=40ll<<20;
__asm__ ("movq %0,%%rsp\n"::"r"((char*)malloc(size)+size));
scanf("%d",&n);
for(int re i=1;i<=n;++i){
scanf("%s",c);
int len=strlen(c);
int now=0;
for(int re i=0;i<len&&c[i]!='#';++i){
int x=c[i]-'a';
if(!son[now][x])son[now][x]=++total;
now=son[now][x];
}
int las=1;
for(int re i=len-1;(~i)&&c[i]!='#';--i){
int x=c[i]-'a';
if(!son[las][x])son[las][x]=++total;
las=son[las][x];
}
to[now].push_back(las);
}
dfs1(1);
dfs(0);
cout<<ans;
exit(0);
}