• 数据结构


    分块&莫队

    (;)

    对询问分块

    (;)
    询问数较少的情况。
    我们记录下当前的(T)次操作,在查询的时候,只需看(O(1))地检查每个操作对查询区间的影响
    这样做到查询是(O(T))
    那么每(T)次操作后,需要重新计算当前全局的局面(如计算前缀和)
    这样总复杂度是(O(mT+n frac{m}{T}))
    (T=sqrt n)时,复杂度最优为(O(msqrt n))

    P3863

    (;)
    考虑扫描线,扫序列的维度,数据结构维护时间轴
    那么一次修改可以看作平行于x轴的一条线,扫的过程中,如果发现有一条线的左端点,要支持后缀修改(会影响后面的时间),代表序列的这个位置相比上个位置+x
    那么我就可以快速的去维护一个位置在所有时间的值,转化为一个区间有多少个数(<x),直接分块来做
    区间加,区间rank问题

    区间出现次数平方和

    (;)
    注意,莫队的复杂度是(nsqrt m),而不是(msqrt n)
    所以询问越多,它跑的越快

    AHOI2013作业

    (;)
    四个限制,莫队维护了两维,直接树状数组会多个log
    但发现修改比查询多的多,于是考虑根号复杂度平衡,支持一个修改(O(1)),查询(sqrt n),直接分块就可以了
    发现莫队其实就是个二维的扫描线
    (;)

    历史研究

    (;)
    发现可能的答案只有(n)
    先处理出所有答案,离散化后放到一个序列上,并对其进行分块
    在跑莫队的时候,把其对应的答案放到我们处理出的那个序列上
    这个需要支持(O(1))插入,然后查询时从大到小,若有元素再在这个块中找,显然是(sqrt n)

    (;)
    出现了两个区间,且是出现次数的乘积。
    发现维度太多,想象能不能消去一些。
    发现这东西是有差分性的
    (f(l_1,r_1,l_2,r_2)=f(1,r_1,l_2,r_2)-f(1,l_1,l_2,r_2)=f(1,r_1,1,r_2)-f(1,r_1,1,l_2)-f(1,l_1,1,r_2)+f(1,l_1,1,l_2))
    中间应该是一些-1这里我没写
    然后就只有两维了,用莫队直接维护,维护两个序列,新增一个点时,看它在另一个序列的出现次数就行了

    CF1476G

    (;)
    发现出现次数最多有(sqrt n)种。在莫队的时候处理出现次数的序列,然后双指针扫就可以

    经典题目

    (;)
    (n)个点(m)条边的无向图,支持单点修改,求和一个点相邻的所有点的点权和
    (m)是1e5级别,所以度数的总和不超过(2m),考虑根号分治。
    度数(<sqrt n)的暴力处理,对于度数(>sqrt n),数量很少,在修改的时候,直接处理这个点的修改对度数大于(>sqrt n)的点的影响

    (;)
    插入(x),求序列中(mod;y)最小值
    (y<sqrt n),对于每个数,预处理出其mod y的值,在询问时,直接取个全局min
    (y>sqrt n),把序列从小到大排序后,分块。对于(y,2y,...,ty)分别求大于它的最小的数。直接在块上跳就行

    平衡树 & 线段树

    (;)
    两种:维护序列权值,查询k小值;插入删除,翻转一段区间

    Splay

    (;)
    rotate:如果三点共线,就先旋父亲,否则先选自己。最后再旋转自己
    merge:把靠左的区间的最右的点旋到根节点,然后直接把右区间接在它的右儿子上
    split:把第k小的旋到根节点,然后直接拆

    P4198

    (;)
    每个点算个斜率
    维护一下区间斜率最大值,有点像cdq分治,考虑左边对右边的影响,这样合并的时候最多递归其中一个儿子,因为另一个儿子已经被另一个儿子算过了,pushup复杂度是log的

    P4036

    (;)

    有单点插入,一眼平衡树
    查询时,直接二分。然后把长度为mid都旋成一颗子树中,那么只需要看两颗子树的哈希值是否相同
    支持平衡树单点插入,维护区间哈希
    一次查询操作是O(log^2 n)

    (;)
    和那到cf div1 B 有点像
    先去维护区间min,max
    直接去维护区间差分数组的gcd,至少需要是k的倍数
    判重:维护每个点的pre。代表上一个a_j=a_i的地方
    维护一个pre的最大值

    P3215

    (;)
    括号序列匹配
    一定是先把能匹配则匹配,剩下的就是"))))(((((",这样的东西
    然后合并就是看左括号,右括号哪一个多。
    invert就再维护一个序列,rev暂时也是,但lxl说可以直接打标记

    李超线段树

    (;)
    在每个点上打个永久化的标记。
    来了一个新的线段后,考虑把那个较高的部分较短的线段下放,这样一定只会下方到其中一个儿子中。
    这样每个线段会被下放log次
    例题:P4097,P4069

    P5608

    (;)
    考虑标记的合并,和值和值得合并都很好做。
    但是标记对值得影响不好做。
    考虑维护极长的"x"段,这样不同长度的段最多有根号种
    如果维护一个光速幂,一个节点计算的复杂度为(O(sqrt {size}))
    虽然为log个节点,但为同一层数最多有2个节点,计算下来还是根号
    至于合并的时候,直接把两边的多项式归并

    线段树/平衡树要注意的问题

    (;)
    标记对标记可合并
    标记对值的影响快速计算
    值对值(儿子的值可以快速合并到父亲)

    区间赋矩阵,区间乘法

    (;)
    线段树直接按2的整数幂建
    那么线段树上每个节点的大小也是(2^x)的形式
    所以对于(m)种操作的每一个矩阵,都只用预处理(log(n))种幂
    一个log的复杂度

    CF453E

    (;)
    我们发现,每一次询问,都会让一个段变为0
    不妨维护一段一段的区间,每个区间上记录它是什么时候变成0的
    那么一次询问,一定会让区间的段数变小(也有可能+1,但并无大碍)
    所以维护一颗平衡树
    那一段一段的去解决,总均摊是O(n)的段数
    而对于每一段说, 对于已满和未满的分别考虑
    已满的就是(t_i<t)(m_i)的和
    把这个问题看作一个二维平面的问题
    把询问离线下来,用扫描线支持单点修改,区间查

    T-shirt

    (;)
    对于人建一颗平衡树,对于一件t恤,相当于要把整颗平衡树中(geq c)的数(-c)
    但如果直接把那课子树打上标记,可能导致不满足二叉搜索树性质。
    所以对于([c,2c])的部分暴力删除+插入,其余部分打标记。
    这样暴力部分最多会有log次,且其余也能满足性质,然后去单点+1(树状数组)
    复杂度:两个log

    P5610

    (;)
    对于这种除(x)的操作,先要去关心值域。
    发现值域很小,就把每种(x)它的倍数处理出来
    然后对于/x,在x这颗平衡树里找([l,r])这段区间,如果发现已经不满足条件了,就直接把它从这棵树中删去
    这样总点数是(n; log; n),均摊后总复杂度是(log^2 n)

    CF438D

    (;)
    区间取模,区间和
    容易发现,一个数对(p)取模之后,值至少会减半。
    所以我们维护区间最大值,如果发现(geq p),那么直接暴力去递归。
    这样每个点最多取模(log)次,且在线段树上递归一个点的时候复杂度也是(log)
    总复杂度:两个log

    HDU6315

    (;)
    区间(a_i)+1
    询问(sum_{i=l}^r frac{a_i}{b_i}),注意这里是下取整
    和上道题类似,同样是抓住所有点的答案的和至多会变(n;log;n)次的性质(调和级数)
    每个点初值为(-b_i),如果发现区间max(<0),那么不需要修改,只用打个标记
    否则我们就要暴力递归,找到0的位置,改成-1,然后把它的答案+1
    也是两个log
    和上道题基本上一模一样的思路

    P7447

    (;)
    ([l,r])(>x)的元素(-x)
    询问和,min,max
    (;)
    一种值域分块方法
    (;)
    分为log个块,分别为([0,1),[1,2),[2,4),[4,8)....)
    那么就可以对于每个块开个线段树/平衡树维护
    也是找到([l,r])在平衡树中的位置
    块的值域(<x):不用管
    块的值域(in[x,2x)),修改后必定会掉到下面的块中,就要删掉,并插在它掉到的那棵平衡树上
    块的值域(geq 2x),维护最小值,看是否要往下掉
    这样最多掉落(log)
    复杂度还是两个log

    P5066

    (;)
    维护所有极长的0,1段
    考虑或操作:让区间内所有1段长度+1,0段长度-1
    与操作是相反的
    所以不妨维护一个区间内0,1段个数,长度的最小值
    如果发现最小值变为0了,肯定往里面递归进行合并,容易发现合并的次数是(O(n))的,所以暴力递归是正确的

    CF1446D2

    找最长的子段,使得众数的个数至少为2
    首先有一个性质,这些众数,其中的一个一定是原序列的众数
    因为从序列两端开始删,第一次有两个数出现次数相等且最大,那么肯定其中一个是原序列的众数。
    假设这个众数是(a),那么我们对于每个值(x),枚举其所有位置,并对其左/右未被标记的第一个(a)打上标记。
    只需考虑那些被打上标记的位置,且只有(O(|x|))
    再搞个前缀和,转化为1,-1,对于每个(sum_i=x),找出最小和最大的(i)即可

    可持久化数据结构

    CF464E

    (;)
    无向图,每条边长度都为2的整数幂
    求s到t的最短路,取模。
    把每个点的dis可以表示出一个01串,用线段树来维护。
    那么比较两个01串的大小直接在线段树上二分,一个log
    在进行(u->v)的转移时,会出现进位的情况。
    于是可能要进行一些区间赋0,单点修改为1的操作。
    在线段树上找到这些节点,新建到新的树上,并把它们影响的父亲也要新建节点
    你会发现这就相当于把一段区间拆分成了log个节点,每个节点到根的路径上的点的并,这样也一定是log个节点(并不是(log^2)个)
    但节点可能不是叶子节点,就不能再往子树内递归去新建节点,这样空间就炸了。
    所以只在上面打上标记,等以后往下递归的时候,再新建儿子节点并下放标记。
    加上dijkstra,复杂度是(O(nlog^2 n))

    一道codechef的题

    (;)
    输出两个树上路径按点权从小到大排序后不同的位置
    发现总不同位置只有1e6
    这里是判断两条路径是否相同,如果在主席树上只维护了某值域中出现了多少个数,不大好做。
    所以再维护一个hash值。
    找的时候,还是运用差分的思想。
    这样我们实际上是对于相同的值域,看它们的位置不同的数量
    在权值线段树找,如果发现hash值不同就暴力递归,因为总共只有1e6不一样,所以是对的

    扫描线

    (;)

    HH的项链

    (;)

    对于区间不同颜色数问题,常见套路:维护(pre_i),代表上一个同一颜色的位置
    问题转化为:查询([l,r])值小于(l)的个数,这个相当于一个3-side的矩形,可差分(很多题往往是离线+差分可以干掉一个关键字)
    扫描线从左往右扫,支持单点修改,区间查询

    bzoj3489

    (;)
    查询区间中有多少数只出现一次
    直接把((x,y))看成区间([x,y]),然后对于一个数(x),找到它的pre和nxt,显然只有在这个区间内的区间在能被它贡献到
    发现又是一个矩形+1,查询单点问题,扫描线解决

    bzoj4212

    (;)
    多个字符串。多次查询,给出字符串s,t:有多少个字符串满足s是其前缀,t是其后缀
    把所有字符串扔到一颗trie上(正着建+反着建)。
    查询时,在前缀trie上找到那棵子树,再在后缀trie上也找到那子树。
    那合法的字符串肯定同时存在于两颗子树中
    对于子树,又是套路,通过dfs序,转成序列上的问题。
    假如一个字符串s,在前缀trie的第x个位置,在后缀trie的第y个位置,那么其可以抽象为平面上的((x,y))
    所以一次询问相当于询问一个矩阵和。
    修改就是单点加

    区间的子区间

    (;)
    把l看作一维,r看作一维,那么所有子区间是一个三角形(y=x以上的),可以看作2-side矩形

    CF997E

    (;)
    多组询问:求一个区间的多少子区间包含连续的一段值域
    转化为:(r-l-(max-min)=0)
    把每个区间都看作平面上的一个点,先构造出这个矩阵。
    如何构造:一个位置可以分为四部分:r,l,max,min,分开考虑
    r,l:比较简单,一行一列的加(其实相当于(1 imes n, 1 imes m)的矩形
    max,min:不妨去分治,找到max/min,然后将序列分成两部分,对应平面上的一个矩形
    按照扫描线的套路,先把这些矩阵加的操作离线下来。
    询问相当于求一个矩阵的和。
    在纵轴上维护一颗线段树,设(b_i)为当前第i个位置历史(a_i)的总和。
    扫描线每往右滑动一次,相当于会改动所有的(a_i)(当前列的和)
    在root上打个tag,即 :(tree_i+=sum_{a_i} imes c)相当于把整个序列的(b_i)都加上了(a_i),还要维护一个历史加的和(这个非常重要),和上次被标记的时间
    那么就可以合并标记了

    P5070

    (;)
    扫右端点,数据结构维护左端点的答案。
    (a_r)的值是(x),那么我们现在只用考虑前面在([x-9,x+9])中的数,对剩余的数显然不会产生贡献。
    还是预处理(pre_i),那么我们首先找到这20个数在([1,r])的最晚出现位置,分成20段
    对于每一段,都相当于,现在已经在长度为20的这个区间里有一些值域连续段了, 然后我们新增了(x),会产生的贡献自然就是合并两边的段产生的长段(或是一个段的开头/末尾)
    所以对于每一个长度(in[1,10]),都要要支持一个区间修改,单点查询,显然维护10个树状数组就行了

    LOJ3489

    (;)
    按照套路,序列和时间分别看做一维。正常来说,这个修改应该是后缀的修改,但这里我们把它改成单点,那么查询相应的改成前缀
    对于询问的前缀,假如说我们可以找到最晚的位置满足队列是空的,那么之后,相当于加入和弹出操作,就与之间的顺序无关了。
    只需要找到插入中第(t+k)个元素(假设共删除了(t)个元素),这个玩意直接在线段树上二分就可以了。
    而最后一个为空的位置,一定是最大后缀和的位置。
    首先往后不可能右空的地方(不然往后移会更优)
    且这个位置一定是空的,不然可以找到从这里往前的一段区间为正,那么从那里开始累加,到这里就会有元素

    LOJ6276

    (;)
    查询树上有多少条路径满足颜色数不同(每种颜色数量$leq(20) 看着像点分治,实际上是诈骗题 考虑同种颜色点)x,y(,)x(子树的点组合上)y$子树中的点的路径都是不可选的。
    按套路求出dfs序,变成了矩形。那么只需要求出所有矩形的并就可以了,然后再取个补集就可以了。
    矩形最多有2e6个,扫描线解决

    KD树

    求区间逆序对

    (;)
    扫描线+kd树
    扫描线扫(r),显然把所有(a_i>a_r)的点的值+1
    查询时,就会求所以(xgeq l)的点求和,就可以kd树了

    NOI2019弹跳

    (;)
    仿照dijkstra的思路,每次找到一个dis最小的点,然后松弛其连到的点
    现在一个点连到的点是一个矩形,太套路了,相当于我们要进行矩形取min操作。
    那么前提是我们找到一个dis最小的点,也就是求全局min。
    结合起来就是kd树来搞

    二进制kd树

    (;)
    反正就是分为了log个kd树,每个kd树的点数都为(2^c)的规模
    在修改和查询的时候,就在这log个kd树上分别去搞,这样是(sqrt n + sqrt{frac{n}{2}}+sqrt{frac{n}{4}}+...+1)也是根号级别的
    那么如果插入了一个点,若发生进位,相当于把若干个kd树合并了起来,变成了一个更大的kd树,那么我们直接暴力去合并。
    这样每个如果被合并了一次,一定代表其所在kd树规模至少乘2,所以一个总合并次数为(n;log;n)
    而对于建立kd树,如果有n个点,其复杂度是(O(nlog n))的,所以总插入复杂度是(O(nlog^2 n))

    树套树

    (;)

    P3380

    (;)
    树状数组套值域线段树。
    主席树是两个点进行整体二分
    现在是log个节点进行整体二分。然后在二分的时候一起往下走
    复杂度是两个log

    P4054

    (;)
    虽然有3个维度(矩阵+颜色)
    但发现颜色具有特殊性,也就是每次询问都是求(a_{i,j}=c)的个数这样的形式。
    对于这样的维度往往可以是省去的。
    发现值域也很小,这里开100个二维树状数组

    动态逆序对

    (;)
    求矩形点权和,和单点修改点权

    P3242

    (;)
    还是按照套路,先搞dfs序。
    那么问题变成了:单点修改,矩阵里求第(k)
    怎么维护呢?
    先把所有矩阵和修改啥的都离线下来,扫描线从左往右扫。
    如果把矩阵改成序列的话,直接就是一个树状数组套值域线段树的事。
    只不过增高了一维。
    按照在整体二分的思想,可以把这个树套树可持久化下来。
    然后对于询问矩阵来说,对于两个版本的树套树差分一下,就可以快速得到这个矩阵中值在([l,r])的数有多少个了
    外层可持久化树套树的整体二分一个log,在内层的树套树中也是一个log。
    (O(nlog^2 n))

    P3242(变式)

    (;)
    反过来想,就是一个矩阵都插入一个数,然后询问单点的第(k)
    因为我只查询单点,问题反而变得更简单一些,就不用可持久化了
    支持区间插入,区间删除,单点查第k大。
    维护一颗线段树套值域线段树,区间插入时,就在线段树上对应的节点所表示的权值线段树的相应值域+1
    因为我们标记永久化了,只用看一下根到它的路径上的所有权值树,也是搞一个整体二分

    bzoj3489

    (;)
    问题转化为矩阵取max,单点求值。
    首先还是离线下来。
    因为我们只关心最大值,所以原先的权值线段树变成了堆
    也就是我们维护了一个树套堆,但这里还要支持删除,堆删除的话就要用到对顶堆
    对顶堆:在原先大根堆A的基础上又维护了一个大根堆B。如果删除的话,先把这个点扔到B堆中。
    在任何时候,如果发现两个堆堆顶相等,就把俩都pop掉。
    容易发现:B是A的子集,且稍微有些双指针的思想

    P5445

    (;)
    把全是1这样的连续段先用set维护一下。
    如果点亮灯,相当于两个段的点联通了,就把一个矩阵上的点加上(inf-t)
    等到之后如果不联通了,就把这个矩阵中的所有数加上(t'-inf)
    这样你会发现刚好加上了(t' -t)
    直接一个矩阵加,单点查历史和的东西。(其实也不用维护历史信息,因为那个操作已经自动累加了历史联通的时间和)
    一个纯纯的树套树问题

    比赛

    (;)

    A

    (;)

    直接启发式合并(不要看到子树就被固定思维转化到序列上)

    B

    (;)
    为了防止算重,记录一下每种数出现的最左和最右的位置,然后就是带修改的偏序问题
    直接树套树维护(我被卡常了

    C

    (;)

    按询问的角度来看不好做
    我们考虑启发式合并,假设现在的根是(x),下面挂着若干颗子树。
    对每个点维护一个set,在枚举轻儿子子树中所有点时,看一下set中其前驱和后继。
    然后把包含它们的所有区间都要加上x这个LCA(相当于一个矩形)
    但为什么其他点不用管,因为前驱后继所表示的矩形一定是最大的,会把其它矩形给覆盖掉。
    然后就是搞一下矩形求并
    但发现这里是2-side矩形,所以可以把这些(k)个矩形再改成另一形式的(k)个不相交的,就是区间加了,扫描线解决

    LCT

    基本操作

    (;)
    access:不断把它旋到splay的根,从(x)接到(y),还需把(y)旋到树根,然后把(x)替换(y)原先的右子树
    makeroot:先access,然后把其到根节点的重链翻转
    cut:先把(x)变成根,在对y进行access,然后在把(y)旋到splay的根,然后把(y)和其左子树分离
    link:先把(x)变成根,再把y变成其父亲,相当于一条虚边
    isroot:判断其父亲的左右儿子只要有一个是x,那么x就必定不是splay的根
    select:先让x变成根,在access一下y,然后splay维护的就是这条路径了
    注意access的时候要先把其到根路径上的点的标记先下放

    P3203

    (;)
    按弹射关系连边,构成一棵树,支持link和cut
    求其到根的距离就可以了(大概access一下看splay的size)

    P3703

    (;)
    这里染成一种从没染过的颜色是很重要的一条性质
    那么可以把这个路径染色看成抽象成access。
    而与序列颜色段数类比,发现这样access均摊的次数就是(O(n+m))
    然后找x->y的颜色段数。
    因为这里涂色很特殊,是点到根的路径。
    所以一种颜色不可能被分成了两段。
    找出它们的lca,差分一下,注意这里要特判一下距离lca最近的一段颜色,讨论一些情况
    维护一下每个点到根要经过重链的个数,那么子树求最大就转化为dfs序
    【根到路径】联想到access

    P4172

    (;)
    让路径的最大值最小,容易发现只需要找到最小生成树
    而删边维护最小生成树不是很好做。
    还是按照套路,考虑加边。
    如果原先不联通:直接加
    否则,把x->y的路径上最大值的边给去掉。LCT就很好维护了
    【动态维护最小生成树】

    P2387

    (;)
    有两个关键字,求1->n两个关键字max的和最小是多少
    发现这题没有什么查询操作
    那么考虑从小到大枚举(A)的max,相应的可以加入一些边【link】
    最小化b的最大值,也是求在b的图上求最小生成树,和上题一模一样

    loj6038

    (;)
    显然维护一下直径就好了,而合并两棵树,新的直径也一定是原来的4个端点的两个之一
    【动态维护直径+距离】

    P4230

    (;)
    双指针,支持link,cut,就可以找出每个(l)为左端点时,最小的(r),满足其是合法的序列。
    单独考虑以(l)为左端点的序列,相当于是一个加上等差数列,线段树维护

    CF1017G

    (;)
    好妙的一道题
    一个点只会被祖先染黑,加入把一个点染黑,就把这个点权值+1。
    那么一个点被染黑当且仅当到根路径上最大后缀和>0
    考虑染白的操作,求出最大后缀和t,然后把点权减去t,发现这样刚好满足条件(这玩意怎么才能想到啊)

    CF1109F

    (;)
    更妙了
    有环了之后肯定就合法了,还是双指针找到第一个没有环的地方。
    考虑这段区间有多少个点(i),满足([i,r])是一棵树(判断不是森林)
    又是常见套路:点数-边数=1的才是一棵树
    考虑维护这个东西
    扫描线往右扫的时候,直接区间+1,考虑与它相邻的四个位置,在区间上找到这四个位置,分为四段,相应的进行区间-1,-2,-3,-4操作
    最后找有多少个位置满足其值=1,显然也可以用维护区间最小值和个数的方法来做

  • 相关阅读:
    Jquery练手 DEMO 全选 取消 反选 复选框 的实现
    ValidateTemplate(Label lb2, boolean iflag) {
    Android中更新UI的三种方式
    Doodle Android图片涂鸦,具有撤消、缩放、移动、添加文字,贴图等功能。还是一个功能强大,可自定义和可扩展的涂鸦框架、多功能画板。
    怎么用 javascript 实现拖拽
    System.Attribute 类的使用
    javascript 操作Cookie
    导航全局滑动JavaScript
    Sql server DATEADD() 函数
    类序列化
  • 原文地址:https://www.cnblogs.com/czyty114/p/15079640.html
Copyright © 2020-2023  润新知