「UR #20」機器蚤分組
題目
點這裡看題目。
分析
定義 \(a\le b\) 當且僅當 \(a\) 為 \(b\) 的子串,題目就是要求 \(S[l:r]\) 的所有本質不同的子串和 \(\le\) 構成的偏序集的最小鏈覆蓋中鏈的條數。
熟練地使用 Dilworth 定理,我們轉而求最長反鏈的長度。注意到,字串作為元素,自帶長度的區分。根據我們的經驗,按照這個度量去劃分很有可能可以得到最長反鏈——因為,長度相等的子串構成的集合一定不存在偏序關係。所以,可以猜到:
結論 I:
最長反鏈的長度即為最大的同一長度的本質不同子串個數。
證明移步 UOJ 題解,這邊不想管了。
此時已經可以匯出一個 \(O(nq)\) 的做法,然而還需要接著優化。簡單嘗試,可以感知到硬來優化是行不通的——換言之,應當繼續尋找性質。
難點就在於:下面的性質不容易猜出來(至少我沒有明確的線索)。這裡只能直接藉助 UOJ 題解來理解:
結論 II:
最長反鏈長度 \(\ge k\) 當且僅當長度為 \(n-k+1\) 的子串全不相同。
證明:
充分性顯然。對於必要性進行反證法,如果存在 \(S[p:p+n-k]=S[q:q+n-k]\),則可以直接翻譯為 \(\forall 0\le d\le n-k,S[p+d]=S[q+d]\)。對於任意的 \(l\le n-k+1\),我們可以得到長度為 \(l\) 的本質不同的子串個數至多為 \((n-l+1)-(n-k+1-l+1)=k-1\),矛盾。
那麼,根據結論,對於一個確定的字串 \(S\)
回到子串的問題上來。對於原串建立字尾樹,則對於某一個字尾 \(S[i:]\),當它和 \(S[j:]\) 的 LCA 確定時,真正有效的 \(j\) 只有兩個:\(<i\) 的最大的 \(j\)、\(>i\) 的最小的 \(j\)。
但是這明顯還不夠,我們接著剔除可考慮的 \((i,j)\) 對。不難想到,如果 \(i_1\le i_2\le j_2\le j_1\),且 \(i_1\le i_2,j_1\le j_2\)
再求解原問題。由於受到 \(r\) 的限制,因此我們需要分別考慮 \(\operatorname{LCP}\) 完全包含於 \([l,r]\) 和部分包含於 \([l,r]\)(也就是伸出去了一部分)的情況。然而這兩種情況都可以輕易地轉化,變為“可掃描線”的問題,此處便不在贅述。
小結:
- 最長反鏈取值範圍的經驗性論斷,以及本題中給出的兩個結論,都挺重要的。
- 剔除無效 \((i,j)\) 對的部分:主動地去想到“剔除”的操作,然後再考察下標之間的關係,通過嚴格的偏序篩掉大量沒有用的對。以及,啟發式合併的思路也值得積累。
- 小心最後“\(\operatorname{LCP}\) 是否完全包含”的討論!
程式碼
#include <set>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int INF = 1e9;
const int MAXN = 1e5 + 5, MAXV = 2e5 + 5;
template<typename _T>
void read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
void write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T Max( const _T &a, const _T &b ) {
return a > b ? a : b;
}
template<typename _T>
_T Min( const _T &a, const _T &b ) {
return a < b ? a : b;
}
typedef std :: pair<int, int> Change;
struct Edge {
int to, nxt;
} Graph[MAXV << 1];
std :: vector<Change> uptL[MAXN], uptR[MAXN];
int tre[MAXN << 2];
std :: vector<int> qry[MAXN];
int qR[MAXN], ans[MAXN], bsc[MAXN];
std :: set<int> ed[MAXV];
int head[MAXV], stId[MAXV], cnt = 1;
int ch[MAXV][26], mx[MAXV], fa[MAXV];
int rt, lst, ntot;
char str[MAXN];
int N, Q;
inline void AddEdge( const int &from, const int &to ) {
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}
inline void Copy( const int &a, const int &b ) {
fa[a] = fa[b], mx[a] = mx[b];
memcpy( ch[a], ch[b], sizeof ch[b] );
}
inline void Expand( const char &c, const int &id ) {
int x = c - 'a', p = lst, cur = ++ ntot;
mx[cur] = mx[lst] + 1, ed[lst = cur].insert( id );
while( p && ! ch[p][x] ) ch[p][x] = cur, p = fa[p];
if( ! p ) { fa[cur] = rt; return ; }
int q = ch[p][x];
if( mx[q] == mx[p] + 1 ) { fa[cur] = q; return ; }
int nq = ++ ntot; Copy( nq, q );
mx[nq] = mx[p] + 1, fa[cur] = fa[q] = nq;
while( p && ch[p][x] == q ) ch[p][x] = nq, p = fa[p];
}
inline void AddPair( const int &l, const int &r, const int &lcs ) {
uptL[l - lcs + 1].emplace_back( r, lcs );
uptR[l - lcs + 1].emplace_back( r, l );
}
inline void Merge( int &a, int &b, const int &lcs ) {
if( ed[a].size() < ed[b].size() )
std :: swap( a, b );
for( const int& x : ed[b] ) {
std :: set<int> :: iterator
it = ed[a].upper_bound( x );
if( it != ed[a].end() ) AddPair( x, *it, lcs );
if( it != ed[a].begin() ) AddPair( * -- it, x, lcs );
}
for( const int& x : ed[b] ) ed[a].insert( x );
ed[b].clear();
}
void DFS( const int &u, const int &fa ) {
stId[u] = u;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa )
DFS( v, u ), Merge( stId[u], stId[v], mx[u] );
}
inline void Upt( const int &x ) {
tre[x] = Max( tre[x << 1], tre[x << 1 | 1] );
}
void Build( const int &x, const int &l, const int &r ) {
if( l > r ) return ;
tre[x] = - INF;
if( l == r ) return ;
int mid = ( l + r ) >> 1;
Build( x << 1, l, mid );
Build( x << 1 | 1, mid + 1, r );
Upt( x );
}
void Update( const int &x, const int &l, const int &r, const int &p, const int &nVal ) {
if( l == r ) { tre[x] = Max( tre[x], nVal ); return ; }
int mid = ( l + r ) >> 1;
if( p <= mid ) Update( x << 1, l, mid, p, nVal );
else Update( x << 1 | 1, mid + 1, r, p, nVal );
Upt( x );
}
int Query( const int &x, const int &l, const int &r, const int &segL, const int &segR ) {
if( segL <= l && r <= segR ) return tre[x];
int mid = ( l + r ) >> 1, ret = - INF;
if( segL <= mid ) ret = Max( ret, Query( x << 1, l, mid, segL, segR ) );
if( mid < segR ) ret = Max( ret, Query( x << 1 | 1, mid + 1, r, segL, segR ) );
return ret;
}
int main() {
rt = lst = ++ ntot;
read( N ), read( Q );
scanf( "%s", str + 1 );
rep( i, 1, N ) Expand( str[i], i );
rep( i, 2, ntot )
AddEdge( fa[i], i ),
AddEdge( i, fa[i] );
DFS( rt, 0 );
rep( i, 1, Q ) {
int l; read( l ), read( qR[i] );
qry[l].push_back( i ), bsc[i] = qR[i] - l + 1;
}
Build( 1, 1, N );
per( i, N, 1 ) {
int n = uptL[i].size();
rep( j, 0, n - 1 )
Update( 1, 1, N, uptL[i][j].first, uptL[i][j].second );
n = qry[i].size();
rep( j, 0, n - 1 ) {
int cur = qry[i][j];
ans[cur] = Max( ans[cur], Query( 1, 1, N, i, qR[cur] ) );
}
}
Build( 1, 1, N );
rep( i, 1, N ) {
int n = uptR[i].size();
rep( j, 0, n - 1 )
Update( 1, 1, N, uptR[i][j].first, uptR[i][j].second );
n = qry[i].size();
rep( j, 0, n - 1 ) {
int cur = qry[i][j];
ans[cur] = Max( ans[cur], Query( 1, 1, N, i, qR[cur] ) - i + 1 );
}
}
rep( i, 1, Q ) write( bsc[i] - ans[i] ), putchar( '\n' );
return 0;
}