爆零蒟蒻今天终于滚粗了......
也就是说,这是北京集训的最后一篇题解了。之后的比赛,我已经没有访问权限了。
T1:
考虑一个暴力做法:f[i][j]表示字符串区间[i,j]的最大等级。
如果k级字符串[i,j]包含一个k-1级字符串s,且s没有达到[i,j]的首(或者尾)的话,我们去掉[i,j]的首(或者尾),剩下的仍然是一个k级字符串。
所以我们可以暴力dp,f[i][j]=max(f[i+1][j],f[i][j-1],max(f[substr])+1)。
而后面的那个可以用后缀自动机枚举出现两次及以上的子串,总复杂度O(n^3)。
显然这并不是正解(然而这对正解有很大的帮助)(要不然我也不会写他)。
考虑我们这样增量(减量?不!)去掉首尾字符的过程,我们一定能保证一个k级字符串的首尾都是一个k-1级字符串。
尾相同的子串有怎样的性质呢?他会出现在当前串的parent树的祖先上。
于是我们可以用f[i]表示后缀自动机第i个节点及其祖先节点中,最大的等级,g[i]表示取到这个等级,所在的最短的节点。
为什么这样做正确?因为对于结尾更靠后的串,我们不计算他的贡献也没有关系是吧,反正他能转移到的串的子串一定能由一个结尾更靠前的串转移到。
转移显然用最短的节点转移最优。我们暴力找头上的那个串出现的位置,看看是否可行即可。
这样我们get到了n^2暴力......
考虑我们现在复杂度的瓶颈在哪里?维护right集合和查询的过程。于是我们可以反向建立主席树并进行启发式合并,复杂度O(nlogn)。
(其实我感觉这东西时间和空间复杂度都是O(nlog^2n)的,只不过跑不满罢了)
代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<queue> 6 #define debug cerr 7 using namespace std; 8 const int maxn=4e5+1e2; 9 10 char in[maxn>>1]; 11 int li,ans=1; 12 13 struct PersistentSegmentTree { 14 static const int maxe = maxn * 25; 15 int siz[maxe],lson[maxe],rson[maxe],cnt; 16 17 inline void insert(int &pos,int l,int r,const int &tar) { 18 if( !pos ) pos = ++cnt; 19 siz[pos] = 1; 20 if( l == r ) return; 21 const int mid = ( l + r ) >> 1; 22 if( tar <= mid ) return insert(lson[pos],l,mid,tar); 23 else return insert(rson[pos],mid+1,r,tar); 24 } 25 inline int merge(int p1,int p2,int l,int r) { 26 if( ! ( siz[p1] && siz[p2] ) ) return siz[p1] ? p1 : p2; 27 int ret = ++cnt; siz[ret] = siz[p1] + siz[p2]; 28 if( l == r ) return ret; 29 const int mid = ( l + r ) >> 1; 30 lson[ret] = merge(lson[p1],lson[p2],l,mid); 31 rson[ret] = merge(rson[p1],rson[p2],mid+1,r); 32 return ret; 33 } 34 inline int query(int pos,int l,int r,const int &ll,const int &rr) { 35 if( !pos ) return 0; 36 if( ll <= l && r <= rr ) return siz[pos]; 37 const int mid = ( l + r ) >> 1; 38 if( rr <= mid ) return query(lson[pos],l,mid,ll,rr); 39 if( ll > mid ) return query(rson[pos],mid+1,r,ll,rr); 40 return query(lson[pos],l,mid,ll,rr) + query(rson[pos],mid+1,r,ll,rr); 41 } 42 }tree; 43 44 namespace SAM { 45 int ch[maxn][26],fa[maxn],len[maxn],deg[maxn],last,root,cnt; 46 int rit[maxn],pos[maxn],roots[maxn],bst[maxn],f[maxn]; 47 int seq[maxn],qlen; 48 49 inline int NewNode(int li) { 50 len[++cnt] = li; 51 return cnt; 52 } 53 inline void extend(int x,int at) { 54 int p = last; 55 int np = NewNode(len[p]+1); rit[np] = pos[np] = at; 56 while( p && !ch[p][x] ) ch[p][x] = np , p = fa[p]; 57 if( !p ) fa[np] = root; 58 else { 59 int q = ch[p][x]; 60 if( len[q] == len[p] + 1 ) fa[np] = q; 61 else { 62 int nq = NewNode(len[p]+1); 63 memcpy(ch[nq],ch[q],sizeof(ch[q])) , fa[nq] = fa[q] , pos[nq] = pos[q]; 64 fa[np] = fa[q] = nq; 65 while( p && ch[p][x] == q ) ch[p][x] = nq , p = fa[p]; 66 } 67 } 68 last = np; 69 } 70 inline void build() { 71 last = root = NewNode(0); 72 for(int i=1;i<=li;i++) extend(in[i]-'a',i); 73 } 74 inline void topo() { 75 for(int i=1;i<=cnt;i++) if( fa[i] ) ++deg[fa[i]]; 76 queue<int> q; 77 for(int i=1;i<=cnt;i++) if( !deg[i] ) q.push(i); 78 while( q.size() ) { 79 const int pos = q.front(); q.pop() , seq[++qlen] = pos; 80 if( pos == root ) continue; 81 if( rit[pos] ) { 82 int t = 0; 83 tree.insert(t,1,li,rit[pos]); 84 roots[pos] = tree.merge(roots[pos],t,1,li); 85 } 86 roots[fa[pos]] = tree.merge(roots[fa[pos]],roots[pos],1,li); 87 if( !--deg[fa[pos]] ) q.push(fa[pos]); 88 } 89 reverse(seq+1,seq+1+qlen); 90 } 91 inline void getans() { 92 f[root] = 1 , bst[root] = root; 93 for(int i=2;i<=qlen;i++) { 94 const int now = seq[i] , milen = len[fa[now]] + 1; 95 if( fa[now] == root ) { 96 f[now] = 1 , bst[now] = now; 97 } else { 98 bst[now] = bst[fa[now]] , f[now] = f[bst[now]]; 99 const int ql = pos[now] - len[now] + len[bst[now]]; 100 const int qr = pos[now] - milen + len[bst[now]]; 101 if( tree.query(roots[bst[now]],1,li,ql,qr) ) f[now]++ , bst[now] = now; 102 } 103 ans = max( ans , f[now] ); 104 } 105 } 106 } 107 108 int main() { 109 scanf("%s",in+1) , li = strlen(in+1); 110 SAM::build() , SAM::topo(); 111 SAM::getans(); 112 printf("%d\n",ans); 113 return 0; 114 }