国际惯例的题面:
考虑我们求解出字符串uvu第一个u的右端点为i,第二个u的右端点为j,我们需要满足什么性质?
显然j>i+L,因为我们选择的串不能是空串。
另外考虑i和j的最长公共前缀(也就是说其parent树上lca的len),为了保证他们相同,我们需要:
j-len>=i-L。
整理一下,如果我们已知i,j需要在区间[i+L+1,i+L+len]中。
如果我们已知j,i需要在区间[j-L-len,j-L-1]中。
于是我们可以写n^2暴力了:暴力维护parent上每个节点的right集合,对于每个i,暴力向上跳,暴力找可行的j。
然后我们发现,维护right集合可以用主席树启发式合并做,对于计算贡献,我们可以先枚举lca,然后计算有多少组可行的i,j。
在第二个计算的时候,我们显然是会在两颗主席树进行合并的时候进行计算,于是我们可以把较小的那颗拍扁,在另外一棵里暴力查询每一个值的贡献。
这样总复杂度O(nlog^2n),轻松AC。
注意主席树启发式合并的时间和空间复杂度都是O(nlog^n)的,因为考虑每层摊还下来只会被新建logn次(我已经把长度和节点个数乘起来了)。
另外这题字符集大小为全体可见字符,所以需要用map存后缀自动机。
(不是很明白为什么网上那么多题解都是后缀数组的,明明后缀自动机这么好写(不会后缀数组的就不要说话了.jpg))
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<map> 4 #include<queue> 5 using namespace std; 6 const int maxn=1e5+1e2,maxl=18; 7 8 char in[maxn>>1]; 9 int li,lim; 10 int seq[maxn>>1],seqlen; 11 long long ans; 12 13 struct PersistentSegmentTree { 14 int lson[maxn*maxl<<1],rson[maxn*maxl<<1],siz[maxn*maxl<<1],cnt; 15 inline void insert(int &pos,int l,int r,int tar) { 16 if( !pos ) pos = ++cnt; siz[pos] = 1; 17 if( l == r ) return; 18 const int mid = ( l + r ) >> 1; 19 if( tar <= mid ) insert(lson[pos],l,mid,tar); 20 else insert(rson[pos],mid+1,r,tar); 21 } 22 inline int merge(int p1,int p2,int l,int r) { 23 if( ! ( siz[p1] && siz[p2] ) ) return siz[p1] ? p1 : p2; 24 int ret = ++cnt; siz[ret] = siz[p1] + siz[p2]; 25 if( l == r ) return ret; 26 const int mid = ( l + r ) >> 1; 27 lson[ret] = merge(lson[p1],lson[p2],l,mid) , 28 rson[ret] = merge(rson[p1],rson[p2],mid+1,r) ; 29 return ret; 30 } 31 inline int query(int pos,int l,int r,const int &ll,const int &rr) { 32 if( !pos ) return 0; 33 if( ll <= l && r <= rr ) return siz[pos]; 34 const int mid = ( l + r ) >> 1; 35 if( rr <= mid ) return query(lson[pos],l,mid,ll,rr); 36 else if( ll > mid ) return query(rson[pos],mid+1,r,ll,rr); 37 return query(lson[pos],l,mid,ll,rr) + query(rson[pos],mid+1,r,ll,rr); 38 } 39 inline void dfs(int pos,int l,int r) { 40 if( !pos ) return; 41 if( l == r ) return void(seq[++seqlen]=l); 42 const int mid = ( l + r ) >> 1; 43 dfs(lson[pos],l,mid) , dfs(rson[pos],mid+1,r); 44 } 45 inline int getsiz(int pos) { 46 return siz[pos]; 47 } 48 }segt; 49 50 namespace SAM { 51 int fa[maxn],len[maxn],deg[maxn],last,root,cnt; 52 int rit[maxn],roots[maxn]; 53 map<int,int> ch[maxn]; 54 inline int NewNode(int ll) { 55 len[++cnt] = ll; 56 return cnt; 57 } 58 inline int extend(int x,int rr) { 59 int p = last; 60 int np = NewNode(len[p]+1); rit[np] = rr; 61 while( ch[p].find(x) == ch[p].end() ) ch[p][x] = np , p = fa[p]; 62 if( !p ) fa[np] = root; 63 else { 64 int q = ch[p][x]; 65 if( len[q] == len[p] + 1 ) fa[np] = q; 66 else { 67 int nq = NewNode(len[p]+1); 68 ch[nq] = ch[q] , fa[nq] = fa[q]; 69 fa[np] = fa[q] = nq; 70 while( p && ch[p][x] == q ) ch[p][x] = nq , p = fa[p]; 71 } 72 } 73 return last = np; 74 } 75 inline int query(int root,int i,int samelen) { 76 if( samelen < 1 ) return 0; 77 int ret = segt.query(root,1,li,i+lim+1,i+lim+samelen); 78 if( i - lim - 1 > 0 ) ret += segt.query(root,1,li,i-lim-samelen,i-lim-1); 79 return ret; 80 } 81 inline void topo() { 82 for(int i=1;i<=cnt;i++) if( fa[i] ) ++deg[fa[i]]; 83 queue<int> q; 84 for(int i=1;i<=cnt;i++) if( !deg[i] ) q.push(i); 85 while( q.size() ) { 86 const int pos = q.front(); q.pop(); 87 if( pos == root ) continue; 88 if( rit[pos] ) { 89 ans += query(roots[pos],rit[pos],len[pos]); 90 int t = 0; segt.insert(t,1,li,rit[pos]); 91 roots[pos] = segt.merge(roots[pos],t,1,li); 92 } 93 if( segt.getsiz(roots[fa[pos]]) < segt.getsiz(roots[pos]) ) // We won't use roots[pos] again . 94 swap(roots[fa[pos]],roots[pos]); 95 seqlen = 0 , segt.dfs(roots[pos],1,li); 96 for(int i=1;i<=seqlen;i++) ans += query(roots[fa[pos]],seq[i],len[fa[pos]]); 97 roots[fa[pos]] = segt.merge(roots[fa[pos]],roots[pos],1,li); 98 if( !--deg[fa[pos]] ) q.push(fa[pos]); 99 } 100 } 101 } 102 103 int main() { 104 scanf("%d%s",&lim,in+1) , li = strlen(in+1); 105 SAM::last = SAM::root = SAM::NewNode(0); 106 for(int i=1;i<=li;i++) SAM::extend(in[i],i); 107 SAM::topo(); 108 printf("%lld ",ans); 109 return 0; 110 }