小H和遺蹟
阿新 • • 發佈:2020-08-09
解:這個題滿足一個結論,假設我們當前要比較字串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; typedeflong 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; }