小H和遗迹
解:这个题满足一个结论,假设我们当前要比较字符串A和字符串B是不是满足的,假设l1为字符串开头位置到第一个‘#’的前缀,s1为字符串倒序中到最后一个'#‘的后缀。
同理l2和s2.如果字符串A和字符串B是满足题目条件的,那么l1和l2中,肯定有一个是另外一个的前缀。s1和s2中,肯定有一个是另外一个的后缀.
首先需要建立两颗trie树,正序一颗,后序一颗。对于每一个字符串,我们保存当前前缀中第一个'#'之前的前一个坐标x1和当前后缀中最后一个'#'后面的第一个点的坐标x2,然后建图保存x1->x2。
当前这个人的贡献 就是 后序trie树上这个节点v的祖先节点中已经储存了多少人 + 这个节点所代表的子树中已经储存了多少人。正好对应了当前这个后缀是别人的后缀 以及 别人的后缀 是 当前这个后缀 的后缀 这两种情况。
具体的代码实现:
对后序字典序跑一个dfs序,因为需要用到区间的信息。每一次查询的时候,对于当前 后序trie树上这个节点v的祖先节点中已经储存了多少人 的相对应的信息,
可以开一个树状数组维护这个节点到根节点的人的个数。对于 这个节点所代表的子树中已经储存了多少人 这个信息,我们需要另外再开一个树状数组维护这个区间的信息(如果用一个的话会算重),这个区间对答案的贡献就是以当前节点所代表的的子树中有多少人... trie不是很熟悉...题解参考
#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; }