CF上3000的数据结构题
废话可能不少,很多时候跟本题可能无关只是积累一下trick
本文所有例题都来自于lxl山东二轮省集Day2课件
CF526F
题意:给一张 \(n\times n\) 的棋盘,有 \(n\) 个棋子在上面,每一列每一行有且只有一个,问有多少个 \(k\times k\) 的正方形里面恰好有 \(k\) 个棋子, \(n\le 3\times 10^5\) 。
\(solution\) :由于每一列都只有一个棋子,所以很自然的可以拍平成一维:\(a_x=y\) 代表在 \((x,y)\) 这个位置有一个棋子,而恰好有 \(k\) 个就可以转化为:\(y-x=(\max_{i=x}^ya_i)-(\min_{i=x}^ya_i)=k-1\) 。于是经典trick就是将每个位置的贡献区间表示为一个矩形,比如\(i\)这个位置作为最大值的区间是 \([l,r]\) ,那么这个位置的贡献矩形就是 \([l,i],[i,r]\) (谁竖着谁横着看题),然后查询一个区间 \([l,r]\) 就是查询这个点坐标,然后就可以用扫描线一样的东西去做了。(二维变一维又变二维挺妙的)。在这个题里面我们不需要做扫描线,因为最大值这个东西被更替就意味着之前那个最大值结束了,所以我们扫右端点,用数据结构维护每个左端点对应的 \(\max-\min-(y-x)\) (这个东西等于0就说明是符合的),至于这个右端点可以更新哪些左端点可以用单调栈去做,然后数据结构可以用线段树。但我们怎么维护\(0\)的个数呢?实际上最小值就是 \(0\) ,这个很显然就不说了。所以你维护最小值个数就可以(因为自己这个区间一定是满足等于 \(0\) 的,所以 \(0\) 一定是存在的)。那个 \(y-x\) 可以每次都 \(-1\) 就好了。这个东西很好写然后没写,写了另一种做法。
就是说我们分治去做,还是续上之前统计 \(y-x=(\max_{i=x}^ya_i)-(\min_{i=x}^ya_i)=k-1\) 这个东西,只统计目前跨过 \(mid\) 的对数。考虑最大最小都在左边,那么 \(y=x+\max_x-\min_x\) 预处理出所有到 \(mid\) 的后缀的 \(\max\min\) 然后就能 \(O(1)\) 地算出右端点然后看看符不符合要求即可。最大最小都在右边是对称的。然后考虑第二种情况,最小在左边最大在右边。转换条件:\(x-\min_x=y-\max_y\) 然后用双指针。随着左边越来越左,右边满足 \(\min_y>\min_x\) 的 \(y\) 肯定也越来越多,所以是单调往右边移动的。但是 \(\max\) 有可能不符合,所以你还要再用一个指针来善后,用条件做下标即可,然后可能会出来负数所以要加上一个 \(n\)。
第二个做法的 \(code\) :https://codeforces.com/contest/526/submission/118952834
CF464E
题意:一张图,图的边权是 \(2^{w_i}\) ,求从 \(S\) 到 \(T\) 的最短路。 \(1\le n,m\le 10^5\)
\(solution\):考虑直接跑\(dijkstra\)我们需要干什么,实际上我们只需要两个数的相加和比较,接下来考虑的都是二进制下。相加是在原来一个数上加上一个只有一位是 \(1\) 的二进制数,所以想到了可持久化数据结构。相加只需要考虑哪一位变成 \(1\) 和一段区间变成 \(0\)(即进位)。哪一位变成 \(1\)可以在线段树上二分,考虑一个位置后面第一个 \(0\) 的位置,如果左边 \(1\) 的个数满了就去右儿子,否则去左儿子,当然你需要优先考虑是在哪个位置之后。然后区间变成 \(0\) 你可以先造一棵全是 \(0\) 的树,然后把这棵树的一部分接过来。比较你可以先比右儿子,这个东西用 \(hash\) 看一下是否相等,相等就去比左儿子。
\(code\):https://codeforces.com/contest/464/submission/118482611
CF603E
题意:一张无向带权图,初始没有边,依次加入 \(m\) 条边,每次加入后询问存不存在一个边集使得每个点的度数都是奇数,如果不存在输出 \(-1\),如果存在,最小化这个边集中的最大值,输出这个最大值。 \(n\le 10^5,m\le 3\times 10^5\)
\(solution\):
引理:一个边集满足所述条件的充要条件是不存在奇数个点的连通块。
必要性:考虑反证法,如果存在奇数个点的连通块,那么度数之和就应该是个奇数,然而一条边会带来 \(2\) 的贡献,所以度数之和应当是偶数,所以不存在奇数个点的连通块。
充分性:只需要对每一个连通块考虑。考虑一棵生成树,从叶子开始操作,对于一个点,它和父亲之间的边保留,当且仅当它与儿子们的边的数量是偶数。这样我们可以保证除根以外的所有点满足条件。对于根,因为是偶数个点,去除掉根就是奇数个点,每个点度数是奇数,那么除根以外总度数也是奇数,但总度数是偶数,所以根的度数也是偶数。
然后我们用 \(lct\) 维护一下子树信息然后顺带着统计多少个大小为奇数的子树,就能判定是否存在解了。怎么维护子树信息呢?我们可以设一个数组 \(d\) 专门统计虚儿子的贡献。图的边怎么搞?考虑把边变成一个点,然后向两个端点连边,边的权值就是这个点的权值。然后对于新来的一条边一定加入,因为这对于是否存在解是更优的(注意不是最小化最大值),考虑两个端点在同一个连通块里不用管,不在的话考虑两个连通块的奇偶性,偶加偶不亏,偶加奇不亏,奇加奇血赚,所以先加进去。然后考虑慢慢弹边,用堆把所有边从大到小排,对于目前要考虑的边,如果原来有解,删去之后无解了,那么把这条边再加回去,然后答案就是这条边的权值。因为权值比自己小的边留着不留着对最大值都不影响,根据之前的推论全都留着,如果选择删去自己,为了有解就必须选择权值更大的边这对最大值是不优的,所以留自己最好。
\(code\):https://codeforces.com/contest/603/submission/118952156
CF1446D2
题意:给一个序列,求最长的至少有两个众数的子区间最长是多少。\(n\le 2\times10^5\)
\(solution\):考虑这两个众数中一定有一个是整个序列的众数。因为这个子区间肯定是整个序列去掉一段前缀和去掉一段后缀,去掉的过程中有一个数的出现次数与众数的出现次数相等了,这个时候就是答案。
考虑根号分治,对于出现次数大于 \(\sqrt n\) 的数,总的数量肯定不会超过 \(sqrt n\) 个,然后对于每个数都进行一个 \(O(n)\) 的算法:设众数为 \(a\) ,目前操作的数是 \(b\) ,然后把每个 \(a\) 变成 \(1\) ,每个 \(b\) 变成 \(-1\) ,然后其他数变成 \(0\) ,那么问题就变成了求解一个最长区间的区间和是 \(0\) ,然后对于前缀求最前出现的位置,然后对每个前缀减一下就好。
对于出现次数小于 \(sqrt n\) 的数,我们枚举最大出现次数,我们用一个桶统计 \(出现次数的出现次数\) ,每次向右移动右端点,如果移动过来只有一个数的出现次数大于我们枚举的了就移动左端点,每次更新出现次数和出现次数的出现次数,然后出现次数的出现次数如果有大于 \(1\) 的就更新答案。
据lxl说有严格 \(O(n)\) 的算法,要用奇怪的序列线性并查集,不过我不会。
\(code\):https://codeforces.com/contest/1446/submission/119004671
CF150E
题意:给一棵树,求一条边的数量在 \([L,R]\) 范围内的路径,使得中位数最大,求这条路径的两个端点。这里的中位数与一般中位数有区别,如果是偶数个,那么中间两个取偏大的那个。 \(1\le L,R,n\le 10^5,0\le w\le 10^9\) 。
\(solution\):关于中位数最大有个经典trick就是二分中位数,然后大于等于 \(mid\) 的数变成 \(1\) ,否则变成 \(-1\) ,然后就变成了求是否存在一条路径的和大于等于 \(0\) ,直接求最大值即可。这个题里面怎么求最大值呢?考虑点分治,枚举每个点之后根据 \(L,R\) 算出对应的深度,然后用线段树查询最值,然后查完一个儿子之后再去更新每个深度的点的最值,但二分套点分治套线段树就三个 \(\log\) 了,这不好。其实点分治里面有个经典trick叫单调队列按秩合并。我们发现枚举到的点所对应的深度是一个区间,再往下移动一个位置之后区间移动了 \(1\) ,这很像滑动窗口,于是我们可以先处理出每个儿子中的不同深度的最大值,然后移动单调队列来查,处理一下边界问题。但这有个问题,就是每次预处理是 \(O(R-L)\) 的,但其实很多点的深度并不深,不会对那么底下的区间造成贡献,我们可以先预处理出每个儿子往下走所能走到的最大深度,然后从小到大排序,这样我们每次需要预处理的就只需要从 \(\min(mxdep_v,R)\) 这个位置开始的东西,然后 \(\sum mxdep\le\sum siz=n\),于是我们每次就变成 \(O(n)\) 了,总的时间复杂度就是 \(O(n\log^2n)\) 了。常数优化是提前预处理出所有的重心就不用每次求了,这里我懒了没写。
\(code\):https://codeforces.com/contest/150/submission/119768651