CF1491H Yuezheng Ling and Dynamic Tree
- description
- solution
- code
description
题目链接
solution
非常清新的小分块题了
前提:将序列分成n\sqrt{n}n块,每块有n\sqrt{n}n个数,记第iii个块的左右边界为Li,RiL_i,R_iLi,Ri,记第iii个数所在块为blockiblock_iblocki
定义topitop_itopi:节点iii的祖先中,与iii不在同一个块的最大编号点
【这个真的很妙,可以说是这道题的解法考察点了】
问题是如何在al,ra_{l,r}al,r更新时,对某个块xxx内的被覆盖节点i∈[l,r]i\in[l,r]i∈[l,r]的topitop_itopi进行更新??
- 如果更新后的ai<Lxa_i<L_xai<Lx,则aia_iai成为新的满足toptoptop定义的最大编号点【iii的祖先都已经全变成了aia_iai的祖先,再加个aia_iai本身】,topi=aitop_i=a_itopi=ai
- 如果更新后的aia_iai仍然属于块xxx内,则直接链接过去,topi=topaitop_i=top_{a_i}topi=topai
明白了如何维护块内信息,接下来就是对不同块进行操作了
首先考虑修改操作
-
散块(只覆盖了块内部分点):直接暴力修改点的aia_iai值,然后暴力重构,单次操作时间复杂度O(n)O(\sqrt{n})O(n)
-
整块:懒标记tagitag_itagi表示点iii的aia_iai还没减多少【比真实的aia_iai多了tagitag_itagi,未真实操作修改】,但这个时候块内某些点的topitop_itopi可能已经发生了变化,没有实时更新就会影响后面的查询操作,而每次暴力修改又需要整个块的遍历,多个整块几乎就是O(O(O(修改区间长度)))的,无法保证时间复杂度
实际上,发现对于每个块,经过n\sqrt{n}n次整块修改操作后,aia_iai都至少会变为ai−na_i-\sqrt{n}ai−n,一定在当前块前面的块
因此,我们可以维护每个块被修改的次数cntxcnt_xcntx当cntx>ncnt_x>\sqrt{n}cntx>n,就不再暴力修改整块,而是直接整块的区间减法,这个时候的aia_iai一定是其的topitop_itopi,否则就暴力修改
每个块最多修改n\sqrt{n}n次,每次需要O(n)O(\sqrt{n})O(n)遍历修改,一共有n\sqrt{n}n个块,总时间复杂度是O(nn)O(n\sqrt{n})O(nn)的
接着考虑询问操作
找lca\text{lca}lca肯定是倍增暴力啦!这里直接使用暴力爬山法
考虑询问u,vu,vu,v两个点的lcalcalca
-
若blocku≠blockvblock_u\neq block_vblocku=blockv,则选择对应blockblockblock编号较大的点,跳到其toptoptop点
-
若blocku=blockvblock_u=block_vblocku=blockv且topu≠topvtop_u\neq top_vtopu=topv,则两个节点一起跳对应的toptoptop点
-
若blocku=blockvblock_u=block_vblocku=blockv且topu=topvtop_u=top_vtopu=topv,则一直让节点编号大的点跳父亲aaa,直到两点相同为止
【注意在跳的时候,一定要减去所在块的tagxtag_xtagx标记,才能得到真正的top/atop/atop/a】
一个点跳toptoptop的次数最多为nn=n\frac{n}{\sqrt{n}}=\sqrt{n}nn=n次,跳完toptoptop就是在一个块里面的跳aaa,最多也是n\sqrt{n}n次,所以单次询问时间复杂度为O(n)O(\sqrt{n})O(n)
本题最终的时间复杂度为O((n+q)n)O((n+q)\sqrt{n})O((n+q)n)
code
#include <cmath>
#include <cstdio>
#include <iostream>
using namespace std;
#define maxn 100005
#define maxB 320
int n, Q, B, B_cnt;
int a[maxn], block[maxn], L[maxB], R[maxB], cnt[maxB], top[maxn], tag[maxB];void pushup( int x ) {for( int i = L[x];i <= R[x];i ++ ) a[i] = max( 1, a[i] - tag[x] );tag[x] = 0;for( int i = L[x];i <= R[x];i ++ ) {if( a[i] < L[x] ) top[i] = a[i];else top[i] = top[a[i]];}
}void modify( int l, int r, int x ) {if( block[l] == block[r] ) {for( int i = l;i <= r;i ++ ) a[i] = max( 1, a[i] - x );pushup( block[l] );}else {for( int i = l;i <= R[block[l]];i ++ ) a[i] = max( 1, a[i] - x ); pushup( block[l] );for( int i = L[block[r]];i <= r;i ++ ) a[i] = max( 1, a[i] - x ); pushup( block[r] );for( int i = block[l] + 1;i < block[r];i ++ ) {cnt[i] ++, tag[i] = min( n, tag[i] + x );if( cnt[i] <= B ) pushup( i );}}
}int query_top( int i ) { return max( 1, top[i] - tag[block[i]] ); }int query_a( int i ) { return max( 1, a[i] - tag[block[i]] ); }int lca( int x, int y ) {while( 1 ) {if( block[x] < block[y] ) swap( x, y );if( block[x] ^ block[y] ) x = query_top( x );else {if( top[x] ^ top[y] ) x = query_top( x ), y = query_top( y );else break;}}while( x ^ y ) {if( x < y ) swap( x, y );x = query_a( x );}return x;
}int main() {scanf( "%d %d", &n, &Q );B = sqrt( n ), B_cnt = ( n - 1 ) / B + 1;for( int i = 2;i <= n;i ++ ) {scanf( "%d", &a[i] );block[i] = ( i - 1 ) / B + 1;}for( int i = 1;i <= block[n];i ++ ) L[i] = ( i - 1 ) * B + 1, R[i] = min( n, i * B ), pushup( i );for( int i = 1, opt, l, r, x;i <= Q;i ++ ) {scanf( "%d %d %d", &opt, &l, &r );if( opt & 1 ) scanf( "%d", &x ), modify( l, r, x );else printf( "%d\n", lca( l, r ) );}return 0;
}