『MdOI R1』Treequery
題目
點這裡看題目。
分析
如果這道題可以換根,那它就是一道水題,可是換不得。
我們首先考慮 \(p\) 是根的時候應該怎麼做。可以發現,對於所有情況總存在:
\[\bigcap_{i=l}^rE(p,i)=E(p,\text{LCA}[l,r]) \]
這裡我們認為 \(\text{LCA}[l,r]\) 為集合 \([l, r]\) 的點的 \(\text{LCA}\) ,括號決定區間的開或閉。
簡單說明一下。記左邊集合為 \(A\) ,右邊為 \(B\) 。由於 \(\text{LCA}[l,r]\) 是 \(l\sim r\) 的點的 \(\text{LCA}\) ,並且 \(p\) 就是根,所以 \(\text{LCA}[l,r]\)
然後考慮 \(p\) 不是根的時候會怎麼樣。首先我們還是需要得到 \(u=\text{LCA}[l,r]\) 。然後考慮 \(u\) 和 \(p\) 的位置,也可以理解為 " 換根 " :
\(\mathcal{Case 1:}\) \(p\) 是 \(u\) 的祖先。此時可以像上面一樣直接判斷。
\(\mathcal{Case 2:}\)
\(\mathcal{Case 3:}\) \(p\) 不是 \(l\sim r\) 中任何點的祖先。如果將 \(p\) 提做根,此時 \(l\sim r\) 的 \(\text{LCA}\) 就不一定是 \(u\) 了。這裡需要再分一些 \(\mathcal{Case}\) 。
\(\mathcal{Case3.1:}\) \(u\) 不是 \(p\) 作為根的情況下的 \(\text{LCA}\)
如果 \(u\) 不是 \(p\) 作為根的時候的 \(\text{LCA}\) ,就說明 \(p\) 在 \(u\) 的子樹內且 \(v\) 應該比 \(u\) 更深,正如下面這幅圖:
\(\mathcal{Case3.2:}\) \(u\) 是 \(p\) 作為根的情況下的 \(\text{LCA}\) 。此時 \(p\) 就不應該在 \(u\) 的子樹內,且 \(v\) 比 \(u\) 淺。如圖:
這麼分類討論一下之後,問題就比較好解決了。我們只需要解決幾個問題:
\(\mathcal{1.}\) 查詢連續幾個點的 \(\text{LCA}\) 。我們可以對點進行倍增, \(lca[i,j]\) 儲存 \(\text{LCA}[i,i+2^j)\) 。由於 \(\text{LCA}\) 這個運算具有交換律、結合律和等冪性 ( 即 \(\text{LCA}\{a,a\}=a\) ),我們可以花費單次 \(\text{LCA}\) 的時間查詢出連續幾個點的 \(\text{LCA}\) 。如果使用 \(\text{RMQ}\) 查詢 \(\text{LCA}\) 可以做到 \(O(n\log_2n)-O(1)\) 解決。
\(\mathcal{2.}\) 查詢一個點是否是某些點的祖先,我們可以直接查詢這個點內包含的標記點的數量。由於問題中標記點在一個區間,我們可以將樹展開到 \(\text{DFS}\) 序上,並利用可持久化線段樹維護點的字首上 DFS 序的情況,並直接查詢。
\(\mathcal{3.}\) 查詢一個點到某些點的 \(\text{LCA}\) 中最深的那一個。顯然我們可以從起始點進行倍增,每次檢查是否有標記點在下一步的點的子樹中,如果沒有我們就可以跳一下。最終我們可以得到最淺的一個子樹內不包含任何標記點的點,它的父親就是我們的目標。這個過程可以配合倍增 \(\text{LCA}\) 理解。
時間複雜度是 \(O(n\log_2^2n)\) ,瓶頸是問題 \(\mathcal{3}\) ,佔用了兩個 \(\log\) 。
本題的可借鑑之處就在於它的分類討論步驟和多次出現的倍增思想。樹上換根問題,既可以 \(\text{LCT}\) 維護,也可以像這樣分類討論。
程式碼
#include <cstdio>
const int MAXN = 2e5 + 5, MAXLOG = 20, MAXS = MAXN * MAXLOG;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
struct edge
{
int to, nxt, w;
}Graph[MAXN << 1];
int s[MAXS], lch[MAXS], rch[MAXS];
int rt[MAXN], nsiz;
int f[MAXN][MAXLOG], lca[MAXN][MAXLOG];
int lg[MAXN];
int head[MAXN], dist[MAXN], dep[MAXN], DFN[MAXN], siz[MAXN];
int N, Q, cnt, ID, lg2;
void addEdge( const int from, const int to, const int W )
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from], Graph[cnt].w = W;
head[from] = cnt;
}
void addE( const int from, const int to, const int W )
{
addEdge( from, to, W ), addEdge( to, from, W );
}
void DFS( const int u, const int fa )
{
f[u][0] = fa;
dep[u] = dep[fa] + 1, DFN[u] = ++ ID, siz[u] = 1;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
if( ( v = Graph[i].to ) ^ fa )
dist[v] = dist[u] + Graph[i].w,
DFS( v, u ), siz[u] += siz[v];
}
void balance( int &u, const int stp )
{
for( int i = 0 ; ( 1 << i ) <= stp ; i ++ )
if( stp & ( 1 << i ) )
u = f[u][i];
}
int LCA( int u, int v )
{
if( dep[u] > dep[v] ) balance( u, dep[u] - dep[v] );
if( dep[v] > dep[u] ) balance( v, dep[v] - dep[u] );
if( u == v ) return u;
for( int i = lg2 ; ~ i ; i -- ) if( f[u][i] ^ f[v][i] ) u = f[u][i], v = f[v][i];
return f[u][0];
}
int rangeLCA( int l, int r )
{
int k = lg[r - l + 1];
return LCA( lca[l][k], lca[r - ( 1 << k ) + 1][k] );
}
void upt( const int x ) { s[x] = s[lch[x]] + s[rch[x]]; }
void copy( int a, int b ) { s[a] = s[b], lch[a] = lch[b], rch[a] = rch[b]; }
int update( const int x, const int l, const int r, const int p )
{
int cur = ++ nsiz; copy( cur, x );
if( l == r ) { s[cur] ++; return cur; }
int mid = l + r >> 1;
if( p <= mid ) lch[cur] = update( lch[x], l, mid, p );
else rch[cur] = update( rch[x], mid + 1, r, p );
upt( cur ); return cur;
}
int query( const int x, const int l, const int r, const int segL, const int segR )
{
if( ! x ) return 0;
if( segL <= l && r <= segR ) return s[x];
int mid = l + r >> 1, ret = 0;
if( segL <= mid ) ret += query( lch[x], l, mid, segL, segR );
if( mid < segR ) ret += query( rch[x], mid + 1, r, segL, segR );
return ret;
}
int query( const int u, const int L, const int R )
{
return query( rt[R], 1, N, DFN[u], DFN[u] + siz[u] - 1 )
- query( rt[L - 1], 1, N, DFN[u], DFN[u] + siz[u] - 1 );
}
void init()
{
DFS( 1, 0 );
lg[1] = 0;
for( int i = 2 ; i <= N ; i ++ )
lg[i] = lg[i - 1] + ( i == ( 1 << lg[i - 1] + 1 ) );
lg2 = lg[N];
for( int j = 1 ; j <= lg2 ; j ++ )
for( int i = 1 ; i <= N ; i ++ )
f[i][j] = f[f[i][j - 1]][j - 1];
for( int i = 1 ; i <= N ; i ++ ) lca[i][0] = i;
for( int j = 1 ; j <= lg2 ; j ++ )
for( int i = 1 ; i + ( 1 << j - 1 ) <= N ; i ++ )
lca[i][j] = LCA( lca[i][j - 1], lca[i + ( 1 << j - 1 )][j - 1] );
for( int i = 1 ; i <= N ; i ++ )
rt[i] = update( rt[i - 1], 1, N, DFN[i] );
}
int DCA( int u, const int L, const int R ) // Deepest Common Ancestor
{
int tmp;
for( int i = lg2 ; ~ i ; i -- )
if( tmp = f[u][i] )
if( ! query( tmp, L, R ) )
u = tmp;
return f[u][0];
}
int getDist( const int u, const int v )
{
return dist[u] + dist[v] - 2 * dist[LCA( u, v )];
}
int main()
{
read( N ), read( Q );
for( int i = 1, a, b, c ; i < N ; i ++ )
read( a ), read( b ), read( c ), addE( a, b, c );
init();
int P, L, R, lst = 0;
while( Q -- )
{
read( P ), read( L ), read( R );
P ^= lst, L ^= lst, R ^= lst;
int RLCA = rangeLCA( L, R ),
num = query( P, L, R );
if( num == R - L + 1 ) lst = getDist( P, RLCA );
else if( num ) lst = 0;
else
{
int t = DCA( P, L, R );
if( dep[t] > dep[RLCA] ) lst = getDist( P, t );
else lst = getDist( P, RLCA );
}
write( lst ), putchar( '\n' );
}
return 0;
}