细嗦好题
记一下每天做的题,并不一定都是好题。
以后没心情写题时就会来这里记录写过的题。每道题大概也就写个三五行,如果有值得写的题会另外开博客的。
1.12
P3714 [BJOI2017]树的难题
考虑点分治。路径权值和可以拆成俩点到根节点的权值和。但是如果最顶上的颜色相同就会被合并为一个区间,要减去。于是我们将同色和异色分开考虑。
题目给的条件为长度在 \(l\) 到 \(r\) 之间,就是一个滑动窗口。要做什么已经很明显了。
需要注意的是单调队列长度与深度正相关。所以为了使单次单调队列长度最小,我们要尽量先处理最深深度小的子树。
但我没调出来,寄。
P6008 [USACO20JAN]Cave Paintings P
简单计数dp。不难发现总方案为各联通块大小的乘积。上面的水会影响下面的水,所以我们从下面往上做,用并查集判连通块。
P1350 车的放置
简单计数dp。设 \(dp[i][j]\) 表示选到第 \(i\) 列,\(j\) 个车时的方案数。不在同一列可以用循环避免。但原图中不在同一列的要分开处理,因为前面的不一定会影响后面的。
于是我们将图左右翻转,就没了这个问题。这就意味着有 \(j\) 行不能选。转移一下就好。
P5391 [Cnoi2019]青染之心
发现所有的操作可以组成一棵树,我们要做的就是在树上做根到节点的完全背包。时间上没有任何问题。
但并不是完全没有问题,我们要开 \(O(n^2)\) 的空间,而题目并不支持。
发现从转移数组从父亲传给儿子时,有一个儿子可以不用传直接在父亲的原数组上改。猜猜都知道这个儿子选重儿子最优。
于是其他轻儿子像之前说的一样暴力开新数组,遍历完子树后数组回收给下一个子树用。重儿子直接继承父亲数组。那么最多会开 \(logn\) 个数组,因为最多有 \(logn\) 条重链,所以最多只会切换 \(logn\) 次。
1.13
P4211 [LNOI2014]LCA
设 \(num(u,l,r)\) 为 \(u\) 子树中编号从 \(l\) 到 \(r\) 的个数。那么询问的节点 \(u\) 对答案的贡献为 \(dep[u] * num(u,l,r)\) 。它的父亲节点 \(v\) 对答案的贡献为 \(dep[v]*(num(v,l,r)-num(u,l,r))\)。两玩意一加变成了 \(dep[v]*num(v,l,r)+num(u,l,r)\)。以此类推,一路到根,答案变为 \(num(rt,l,r)+...+num(v,l,r)+num(u,l,r)\)。
这实际上很没劲,因为仅仅是跳父亲的操作都因复杂度太高不能被支持。但我们可以用这个式子算一下 \(l\) 到 \(r\) 对答案的贡献。
设想点 \(w\) 在 \(l\) 到 \(r\) 之间,那么他的贡献会在\(num(rt,l,r)\)中+1,会在根的儿子+1,到什么时候不会+1了呢?到LCA(w,u)下就没贡献了。
从LCA往上加贡献太复杂了,但从 \(w\) 往上加贡献很简单。反正加其他节点都不影响答案, 就把 \(w\) 到根节点的路径一路+1,再统计 \(u\) 到根节点的路径权值和。
如果每次询问都这么搞一遍,复杂度还是不够优秀,因为搞完了以后就要清空。我们发现了一件事情,虽然这个方式处理\((l,r)\)要清空,但是处理n个 \((1,l)\) 是不用的。而\((l,r)\),不就是\((1,r)-(1,l-1)\)吗?
于是从1开始一个一个的往路径上加,遇到询问就查,最后一减就完事了。
P5494 【模板】线段树分裂
我们开权值线段树。模仿FHQ裂出前\(k\)项。合并就是从根开始暴力合并,遇到一个为0就直接替换成另一个。若都不为0两者权值相加。
唯一要说的就是题目中的分裂操作。设到 \(x\) 有 \(l\) 个数,到 \(y\) 有 \(r\) 个数,我们模仿FHQ,裂出\([1,x-1]\),接着就可以在后面的数组里裂出\([x,y]\),将这部分给一个新的根,另外两部分合并一下就行。
感性理解复杂度。一次分裂 logn 没的说,合并时因为后面的区间在前面的区间的后面(什么废话),所以前面的树有两颗子树时,后面的树肯定只有右边一棵,反之亦然。由于合并时遇到 \(0\) 我们会退出,所以每次只会留下一棵子树,还是 logn。
P2633 Count on a tree
一开始的想法是无脑的树剖+主席树,后来发现第k小的操作不好合并。
后来一想,主席树本质上就是前缀和(节省时间)+可持久化(节省空间)。树上并不是不能前缀和。于是直接按父亲版本到儿子建立主席树,然后四个版本(u,v,lca,lca的父亲)同时遍历,和常规主席树一样统计就行。
1.15
middle
中位数就是排名为 n/2 的数啊。。。然后我们并不知道哪些数小于中位数哪些数大于中位数。因为我们压根不知道中位数是谁。
这玩意有单调性,能二分啊。。。二分完以后就能统计了。怎么统计呢?我们把大于与小于变成1和-1。然后就能统计了。
那么ab中选出最大权值的后缀,cd中选出最大权值的前缀,就是答案了。。。这就是线段树啊。
等等,这样要开 \(n\) 棵线段树啊,空间会炸。但每次中位数变大 1 只会变一个数的权值诶。。。于是上主席树,就做完了。
我还是第一次知道,void函数是可以传回去一个void函数的诶。。。浪费我一个晚上
P6071 『MdOI R1』Treequery
公共部分就是LCA到根的距离(根是会变的),于是只要我们求出l,r就LCA能求出到根的距离。
看看第一个部分分,根是固定的,LCA有结合律,这不就是线段树吗。。。
考虑换根吧。我们设原来的LCA为 \(L\),当前根为 \(r\)。如果 \(L\) 不是 \(r\) 的祖先,那么LCA还是 \(L\)。原因就是。。。感性理解吧。
如果 \(L\) 是 \(r\) 的祖先,那么有可能有某个点走到 \(L\) 的途中经过了更接近 \(r\) 的点,现在这个点变成LCA了。
于是我们找到在 \(r\) 的祖先中,距离 \(r\) 最近的,子树中有 \([l,r]\) 中至少一个点的祖先。在子树中求这玩意,转成dfs序之后就是裸的主席树了啊。。。
这玩意有单调性,能倍增。然后这题就做完了。
P4197 Peaks
kruskal重构树板子题。我们运行 kruskal算法的时候,合并并查集不是直接合并,而是新建一个节点,将原来的两个根当做这个节点的儿子,边权当做这个点的点权。
这个图被我们转成了一棵树。这棵树是个大根堆(父亲在儿子后加入,所以权值大于儿子),而且很重要的一点是,想要从 \(u\) 走到 \(v\),经过的最大权值的最小值就是这棵树上的LCA。
很简单,因为当LCA被创建时 \(u\) 和 \(v\) 才能被连接起来。为啥不是LCA的祖先呢?他们的权值比LCA还大啊。。。
然后在子树中统计节点,做完了。。。