幻影彭的彩虹

记录青春的扇区

最小生成树 X 动态规划

​ |

​ |

​ |

​ /

​ /

F1: xxxx oooo

…………

最小生成树(MST)

常用算法有 Kruscal 和 Prime,以下会谈一些 MST 的性质和两种算法的证明

Kruscal 证明(参考 OI-wiki)

核心思路是证明任意时刻边集均为一颗 MST 的子集。

归纳的,令当前边集 \(F\) 属于的 MST 为 \(T\),考虑新加入的一条边 \(e\),如果 \(e\in T\),自然成立。

如果 \(e\notin T\),那么 \(e+T\) 构成了一个环,考虑该环上所有边 \(E_i\),一定满足 \(w_{E_i} \le w_e\),否则 \(e\) 应该在 \(T\) 中,得到更优的 MST,所以 \(E_i\) 已经在 \(e\) 之前被加入,构成了一条完整的链。

所以不会加入 \(e\),进行下一步。

一些性质

Prime

动态规划最小生成树

一般这种题都是指数级的题,有 这道这道,和 这道

例题

给你一张图,问任选 \(i\) 条边,使得图联通的方案数有多少种。

Method1

记录联通性动态规划

Mehod2

考虑 \(dp[mask][i]\) 表示子图 \(mask\) 的答案,转移时发现重复了,因为两种不同的子图分配方案最后可能对应到同一种选边方式,所以我们需要一个顺序一类的东西来保证不重复。

一种比较可行的方式是计算记录顺序的情况下的方案,这样可以钦定枚举的两个子图一定是最后连上的,最后除以一个阶乘。

另一种方式是直接容斥,考虑计算选 \(k\) 条边后不联通的答案,枚举得到的两个连通块就是断开的,还是有重复的问题,但是我们可以钦定每种图只会在与 \(1\) 相连的连通块处被统计,这是经典的连通图计数方式。

考试总结

考试

成功垫底

​ 难 T

​ 度 1

​ 倒 磕

​ 序 了

​ 自 一

​ 闭 万

​ 场 年

T1

我不会的题。

求有向图可达点数量实际上不存在低于 \(O(n^2)\) 的算法,所以得考虑给定图的性质。

显然给定的图是个平面图。

然后定义一个东边的点是有效的,当且仅当它能到一个西边的点,一遍 \(BFS\) 可以把有效点找出来。

然后对于所有点,它能到达的有效点集合一定是一段区间,考虑反证,对于某个点,如果被夹在中间的一个有效点到不了,那么如果其它点可以到就只能穿过一些边,这是不可能的。

写拓扑排序是非常傻逼的行为,直接对每个有效点拓展,算出西边每个点到达有效点坐标的最值就行。

T2

会但是考场写不出来的题。

Method1

考虑枚举排列计算 MST,本质上需要求前 \(i\) 个边能将图联通的排列一共有多少个。

可以考虑计算出任选 \(i\) 条边后图联通的选法有多少种,然后通过容斥计算出恰 \(i\) 个边联通的排列个数。

可以记录联通性动态规划搞定。

复杂度 \(O(bell(n)\times m^2)\)

Method2

枚举联通往往可以弄成二进制枚举联通状态,逆序考虑联通块的构成方式,发现求任 \(i\) 条边联通的方案数,转移的时候是不强调顺序的,所以枚举子集会导致重复,不妨直接考虑顺序,计算原问题的答案。

\(dp[mask][i]\) 表示只考虑 \(mask\) 子图,答案为 \(i\) 的排列个数,转移就可以枚举子集,从上一条边的状态转移,需要进行一些排列组合,复杂度 \(3^n\times m^4\)

Method3

高达 \(m^4\) 的多项式复杂度还是太逊了,考虑继续优化。

其实我们很想直接任选 \(k\) 条边,然后排除掉不合法的情况,这样能够求出答案至多为 \(k\) 的个数,容斥之后就没有问题了。

所以考虑如何排除不合法的情况,就是边选好了但是图没联通,这个时候我们就可以枚举一个小连通块,可以用若干边连接内部,若干外部边任选,就是一类不合法的情况,但是这样会算重,我们可以钦定枚举的联通块和 \(1\) 联通。

复杂度 \(O(3^n\times m^2)\)

总结

这一类图论上的二进制枚举问题,往往可以先从枚举联通块入手,再考虑子图的答案,最后综合运用容斥等计数手段,得到复杂度优秀的做法。

T3

非常有意思的题

Method1

考场上的想法,主要受到某道题的启发。

只考虑最大值,\(O(n)\) 可以扫一遍,单调栈维护当前每一段的最大值与和。

很浪费,因为我们本质上对每个右端点都求了合法左端点区间的答案,考虑能不能复用右端点的答案。

其中一种方式是分块,考虑一段右端点到每个左端点的答案的前缀和,然后查询就可以被拆分了。

Method2

为什么我们会分块,回到那道题,我们有办法通过一些预处理结果得到两个区间并的答案,但是无法快速合并两个预处理结果,所以我们分块,只需要预处理一次。

但是利用随机数的性质我们发现其实可以快速合并预处理的结果。

将问题转化为两种基本形式,相离和重合,这是容易的。

先考虑相离怎么做

考虑利用随机的性质,发现任意一段区间,其有效的贡献个数为 \(\ln\) 个,即从区间首或位开始的最长连续上升序列,然后枚举两个 \(\ln\) 的区间合并,搞定相离的情况,视实现是 \(\ln^2\) 或者 \(\ln\),后者常数较大可能并不优秀。

然后是重合,能不能合并两个重合的区间得到新的区间,可以!转化为一个相离和两个更小的重合问题,因为数量是 \(\ln\) 的,所以合并预处理信息是可能的。

但是,为啥要用线段树,因为没有逆元!但是这道题是可以将一个大重合问题减去一个相离和一个小重合问题得到另一个小重合问题的,所以对大重合问题做前缀和即可。

Method3

对于二维统计问题,常常可以考虑固定预处理其中一维,尝试快速通过预处理结果查询第二维。

这道题中,我们可以预处理每个点作为第二个区间中的点时,在第一个区间中每个点的答案。但实际上这个数据量是 \(O(n)\) 的,没有办法快速搞定,那不妨考虑只处理其前缀和与后缀和,看看是否能够压缩信息。

一些不太正确的思考

考虑单个右端点对一段区间查询,考虑区间最大值,如果落在自身或者空区间,那么答案容易计算,如果落在左区间

考虑它前面第一个比它大的位置,如果不存在或者在空区间,那么答案可以通过前缀和快速计算

正确的思考

考虑区间相离的基本情况。由于单点我们都没有办法做,所以不太能直接搞,但是容易发现区间重合时可以搞定的,考虑区间最大值的位置,跨过它的答案容易统计,不跨过它,我们处理了每个点为右端点和左端点时答案的前缀和,那么由于不跨过最大值时,在最大值另一侧的答案完全不受同侧的影响,所以前后缀和减去最大值所在位置的前后缀和就可以得到重合的答案。

有了重合,我们可以解决区间相邻的答案,定义求两个区间答案的运算是 \(\oplus\),那么对于两个相邻区间有\((a+b)\oplus(a+b)=a\oplus a+b\oplus b+a\oplus b\)

我们能搞定的是 \(a\oplus a\),对于相离的区间,转化为相邻区间做 \(a\oplus c = (a+b+c)\oplus (a+b+c)-a\oplus a-b \oplus b-c\oplus c - a\oplus b -b\oplus c\)

因而搞定了所有情况。

实现

如果直接这么写,写出来很丑,实际上由更优雅的写法,考虑差分区间,如果左端点大于右端点那么没有问题,\([l_1,r_2]\) 全部统计,那么会有重复 \([l_1,l_2-1]\) 这一段区间就是被重复统计的,当然这个区间可以不存在,需要减去,然后再减去 \([r_1+1,r_2]\) 这段有可能不存在的区间的答案,最后加上可能被算重的 \([r_1+1,l_2-1]\) 区间。

反正这个思想挺神的,看上去有问题但确实是对的,比直接写优雅很多。

Method4

思路

考虑每个数对答案的贡献,弄出左右两边第一个比它大的数,那么这些区间以内跨过它的,都在它的贡献范围内,左右端点可以弄成 \(x,y\) 坐标,这是一个矩阵加。

考虑查询,本质上也是在查询 \(x,y\) 坐标各在一段区间内的答案。

于是就是一个修改全部在查询前面的矩阵加矩阵查问题,可以用树状数组解决。

实现

说着轻松,但其实还没写过这种东西,简单思考下怎么做。

首先区间查被差分成 \(\ge x,\ge y\) 的区域加一,区间查变成 \(\le x,\le y\) 的区域查询,还是回到了二维偏序问题。

点是 \((x_1,y_1),(x_2,y_2)\),条件是 \(x_2\ge x_1\wedge y_2\ge y_1\),贡献是 \((x_2-x_1)\times(y_2-y_1)\),是不是很阴间?所以多维护点东西,一个 \(cnt\) 树状数组搞定 \(x_2\times y_2\),两个分别加 \(x,y\) 来搞定 \(x_2\times y_1,x_1\times y_2\) ,再来一个统计 \(xy\) 搞定剩下那一项,其实还算好写。

T4

考虑刻画出图的形态,它是一个基环内向树森林。

容易发现只用将 \(1\) 和其它点连边,然后有些点是必须连的,入度为 \(0\) 的,编号非 \(1\) 的点必须连。

先不考虑环,这样连了之后,又有一些点是必连的,而且容易发现连这些点一定最优,所以继续连。

连到不是环上的所有点都连上为止。

现在还剩一些环,环上一些点是合法的,需要用长度为 \(k-1\) 的线段取覆盖环,让所有点合法。

容易发现确定某个起点之后就能贪心了,考虑连续的 \(k+1\) 个不合法点,枚举每一个并确定最少数量,然后取 \(\min\) 就行,因为这连续的 \(k+1\) 个不合法点一定有一个是起点。

总结

考试

T1

挺套路的题,通过 00 11 得到 \(1,0\) 的个数,容易发现 10 01 的总个数已经被确定且容易构造。

但是有边界,考虑 00 11 个数为 \(0\),此时可以取 \(1\) 个也可以取 \(0\) 个。然后 100->55

这种两个构成的计数问题,一定要考虑只有一个的边界情况,

注意我们通过题目信息得到新信息的过程,要看看是否为一一映射。

T2

有趣的题。

我先思考了如果一个问题我限定一个时间 \(T\),等 \(T\) 秒过完,得到的收益,实际上我们可能有根据当前信息改变等待的这个时间,所以这样不行。

接着考虑 \(t\) 时刻到 Trie 上 \(j\) 点的最优方案,发现其实不好转移。

注意到我们能决定的只有回答还是等待,所以这样的话转移会带上一个概率,但是这个概率却是无法相加后取最值的。

正难则反,考虑到 \(j\) 点时还剩时间 \(t\),最优能得到什么,这样的话,转移就只和我们的决定有关,成功解决这道题。

T3

等价于求最优解并构造方案。

它的修改本质上是需要决定一个最值,付出对应的代价,然后如果某个数绝对值严格小于最值,那么这个数的正负性就可以被指定,然后指定最值后最优化代价就是要求出删去的数的最小个数。

我们需要求出对于每个最值的删除数最小个数。

由于不让 \(0\) 出现,所以先判掉不修改只删除的方案,这个是简单的。

有两种思考方式,对应了两种求答案的方式。

一个数为负视为 -1,为正视为 1,可调整视为 0

Method1

考虑一段极长连续相同子段,如果不为 0,那么必须删去然后只剩下一个。

如果为 0,其左右两边的状态和自身长度决定了它应该被删去一个或者不做任何处理。

如果能够维护机场连续相同子段,就可以得到答案。

考虑最值从小到大变化的过程,每次会把一个非 0 数改为 0,经典的 set 维护线段问题。

Method2

考虑动态规划。

\(dp[i][0/1]\) 表示考虑到第 \(i\) 个数,末尾为 \(-1/1\) 的代价。

\[ v_i=0:dp[i][o]=\min(dp[i-1][o\oplus 1],dp[i-1][o]+1)\\ \]

\[ v_i\ne0: \begin{cases} dp[i][v_i]&=\min(dp[i-1][v_i\oplus 1],dp[i-1][v_i]+1)\\ dp[i][v_i\oplus1]&=dp[i-1][v_i\oplus 1]+1\\ \end{cases} \]

然后就是动态 DP,考虑能不能写成矩阵乘法的形式,发现是一个 \(+\min\) 矩阵乘法。

线段树维护即可。

计算几何

写一下学计算几何的感悟。

向量

点积

\(\mathbf{a} \cdot \mathbf{b} = |\mathbf{a}|\times|\mathbf{b}| \times \cos\theta = x_1x_2+y_1y_2\)

常用来计算向量夹角。

叉积

\(\mathbf{a} \times \mathbf{b} = |\mathbf{a}|\times |\mathbf{b}|\times \sin\theta = x_1y_2-x_2y_1\)

几何意义是两个向量围成的平行四边形的面积。

\(\mathbf{a}\) 旋转到 \(\mathbf{b}\),如果角度小于 \(\pi\),那么为正,否则为负。

证明的话,方向是直接定义的,先不管,尝试证明。 \[ (x_1y_2-x_2y_1)^2=\sin^2\theta \mathbf{a}^2\mathbf{b}^2\\ \] 我觉得这个不太适合用 markdown 写下来。

为什么方向是对的,可以替换一下 \(x,y\) 变成 \(\sin,\cos\),然后很自然是对的。

可以用来求线段交点。

算一下 \(BCD,ACD\) 的面积,用相似求 \(AO\),就可以得到 \(O\) 点坐标。

image-20221004210732068

向量旋转

有人不会用这个东西,我不说是谁。

\(\mathbf{a}\)\(\alpha\) 角:\((|\mathbf{a}|\cos(\theta+\alpha),|\mathbf{a}|\sin(\theta+\alpha))\)

和差角公式记不住的话朱某要来找麻烦,所以有人还是能记住。

多边形

三角剖分

\(S=\dfrac{\sum\mathbf{a_i}\times \mathbf{a_{(i+1)\%n}}}{2}\)

盗一波图。

image-20221004211714058

凸包

按照逆时针方向看,多边形凸包永远往左拐。

直线

可以用一个点 \(P\) 加方向向量 \(\mathbf{v}\) 存储,也可以顺便记一个 \(P_2\)

点在直线的哪边

\(PQ\times \mathbf{v}\) 即可,为正在下方,为负在上方,\(0\) 则在线上,前提条件是向量 \(\mathbf{v}\)\(x\) 为正 。

快速排斥实验和跨立实验

image-20221005160200348

两个矩阵不交,则通过快速排斥实验。

比较简易的方式是对于两维独立判断线段是否有交,如果任何一维无交,则通过快速排斥实验。

跨立实验则是判断一条线段的两端是否在另一条线段的两边,可以用向量叉乘做。

相互做跨立实验,如果均能通过,则线段有交或共线,结合快速排斥实验可以判断线段是否有交

两条线段共线但不交也能通过跨立实验。

两直线交点

先判断平行和重合。

然后对于两条直线 \((P_1,\mathbf{a_1}),(P_2,\mathbf{a_2})\),设 \(Q=k\mathbf{a_1}+P_1\),则有 \((P_2-(k\mathbf{a_1}+P_1))\times \mathbf{a_2}=0\)

叉积有分配律,所以直接拆开解 \(k\) 然后带回去。

\(k=\dfrac{(P_2-P_1)\times \mathbf{a_2}}{\mathbf{a_1}\times\mathbf{a_2}}\)

如果你敢约分,朱某就敢把你鲨了。

线和直线的垂足

算出 \(PQ\)\(\mathbf{v}\) 上投影的长度和方向(点积),\(P\) 加上这个就行。

点到直线距离

可以先算垂足,也可以用记下来的另一个点带面积公式算。

角平分线

模长相同的方向向量相加,得到角平分线的方向向量,证明考虑构造菱形。

凸包

于是乎求凸包的时候多了一种判定方式:

如果是上凸包,弹出点的充要条件是上个点在该点与上上个点的连线下方

下凸包,弹出条件就是在上方,比斜率好一点,而且判共线很方便。

线援交圆交

求出点到直线距离和垂足,然后勾股定理算两个点。

圆圆交

半径相等可以求中点勾股定理偷懒,半径不等可以选一个三角形解三角形。

具体的,可以算出一个角的 \(\cos\) 然后旋转向量,最后乘半径再加上去。

切点

勾股定理,旋转,解三角形。

某场考试总结&&OI 中的解析几何

考试

有人几何学的很差,考场上还非要写。

T1

exchange-argument,已经是咱非常了解的题目啦啦啦。

Ex-Ar

T2

套路题,二分然后最短路

T3

对这种题还是比较熟悉了,不好处理的区间问题分类讨论变偏序,二维偏序解决区间交,贡献独立的情况,李超树搞定贡献分离的情况。

当然可以 CDQ 然后凸包上二分,但是我觉得我更擅长一般的数据结构。

T4

我.... 不会解析几何。/ll /ll。

办法其实非常蠢,一条可行的最短路一定经过圆弧交点,且只会在这些交点拐弯。

所以求出所有交点,然后判断交点两两路径是否合法,连边跑最短路。

可行的交点其实不多,得判掉在圆内的点,然后剩下点的个数感觉上是 \(O(n)\) 的,实际也是 \(O(n)\) 的,证明我不会,等问数学竞赛的回来再补。

然后判合法本质上是判一条路径是否被覆盖,需要写线圆交,然后前面要写圆圆交,这玩意不是一次函数,会非常裂开,我写的很丑。

圆圆交直径相同,比较好办,用极坐标可以偷懒,线圆交相当折磨。

计算几何入门

用向量写会相当舒适。

考试总结

考试

T1

经典的 01 分数规划问题,二分小数,直接 DP ,然后记录转移状态,倒推分数得到答案。

注意这种答案可能为 0 的 01 分数规划一定要从 -1 开始二分答案,不然可能会取不到 0

注意到其实它的分子分母都比较小,二分分数也是可行的一种方式 。

考场上的错误其实挺离谱的,就不细说了。

二分分数不用倒推,就很简单了。

T2

套路题。

观察发现如果每张牌个数都大于等于 \(2\),那么一定合法。否则一定会用一些顺子来凑,同一个位置最多被三个顺子用,所以如果一个位置超过 \(5\) 个,可以视为 \(5\) 个,状压 \(DP\) 出合法的状态,对每种状态计算答案并求和。

拿到状态后计算答案就是个组合数。

T3

0927考试总结

考试

T1

题意超级钢琴。

真的是非常经典的题目了,可以说是开创了处理同一个问题 \(1-k\) 优解的先河。

将解空间拆分成二维,枚举其中一维,考虑第二维的选择并计算答案,用 priority_queue 选择最优答案,选择答案后将当前的第一维按第二维剩下的情况拆分。

动态点分治也是一样的思路。

T2

非常经典的题目,严格递增转不降是经典套路,最优解一定取到每个数值也是经典结论。

本题保证数据随机,应该是需要利用随机序列单调上升子序列长度为 \(O(\sqrt n)\) 的性质,但实际上可以 \(O(n^2)\) 轻松通过。

哦,应该是考虑补集转化,然后考虑哪些点固定,然后 \(dp_i\) 表示第 \(i\) 个点固定的最优代价,转移只能从能完成 LIS 转移的点转移过来,中间合法的条件是要么大于右边要么小于左边,根据经典结论一定可以调整成一段和左边相等,一段和右边相等,然后随机的情况下转移点个数 \(O(1)\),长度 \(O(\sqrt n)\),总复杂度 \(O(n\log n+n\sqrt n)\)

简单证明经典结论

假设有一种最优解包含了不是原有数的数。

考虑第一个不是原有数的数,如果它被调大了,那么把它调小成上一个原有数一定更优。

如果它被调小了,就把它调到第一个比调整后的数大的原有数的位置,如果因此导致后面若干个数小于它了,那么不难得知这些数现在都被夹在两个原有的数之间,而且都是从两个原有数区间的外面调整过来的。从左边第一个数开始,找和它相同的数,如果相同的数中,被调小的数较多,那么全部调到第一个和它不同的数,否则调回第一个原数,变成子问题。

T3

最大值最小显然二分。

考虑一个划分合法的条件,发现只能在某些合法的位置断开,而且在这些位置是否断开不影响第一个条件的合法性。

所以考虑对这些位置动态规划,直接转移是 \(O(n^2)\) 的,然后考虑维护所有可能的转移,发现如果拿个单调栈记录 \(a\),那么转移的改变可以描述为 \(O(n)\) 次区间加,线段树维护即可。

一个数组,考虑每个前缀的后缀最大值,可以被描述为 \(O(n)\) 次区间加。

20220924 考试总结

考试

T1

中规中矩的动态规划题。但我选择读错题。

求最长周长可以用一列一列考虑,处理出每个点向上延申的最上面的位置,用单调栈维护这个东西,然后可以做到 \(O(点数)\) 处理。

T2

又是逆向考虑的标准题目。

考虑最终答案的直径,对于直径这种东西,它的中点是非常特殊的,因为从中心到两边的距离是相同的。

这道题又要最小化直径生成树,也就是最小化中心到两边的距离,那从中心到某个点最小距离的最大值恰好就是直径的一般,所以对每个点跑一遍 BFS,求出 BFS 树的直径即可。

不难得到一个结论:最小直径生成树的直径中点(有可能在一条边上)是图的绝对中心,其中图的绝对中心可以存在于一条边上或某个点上,该中心到所有点的最短路的最大值最小。

T3

算是套路的反向转对穿。

称将反向视为对穿后形成的局面为对照局面,那么对照局面的周期为 \(2L\),故问题周期为 \(2L\),转化为 \(T\le 2L\)

考虑往回走比较恼火,转化为撞墙后继续走,然后有一个相同颜色的人在此时出发,即在最初在一个对应的位置准备出发,容易发现仍然没有两个点在同一位置。

扩大了 \(n,m\) 的范围以及数轴的考察范围,但是不用考虑转向了,难度实际上降低了。

由于有颜色差别,所以可以分出两种思考方向。

换衣服

两个 Heren 相遇后换衣服,从左往右依次考虑每个向左的 Heren,除了第一次之外,都是右边的 Heren 在相邻之间换衣服(向左的那个 Heren 和第 \(i\) 个换衣服之后马上和第 \(i+1\) 个换),相邻向左的 Heren 换衣服是可以快速计算区间答案的(或者说用扫描线后是均摊 \(O(1)\) 的)。

回到初始局面

考虑将对照局面的相遇和原局面的相遇,只需要求出相遇位置的排名,就可以对应回原局面的情况,注意从 \([0,L]\) 之外出发的点,是什么颜色其实并不重要,因为排名落在这些区间的相遇,一定在 \((0,L)\) 外面,注意是开区间,所以还需要妥善处理边界。

发现每一个向左的点的所有相遇事件,其位置的排名单调递增,对应的坐标也单调递增,回到原序列相邻元素是一个区间加。

思考这个方式的本质,和换衣服的思路没有太大差异。

敲下"相遇"的时候,差点又掉眼泪....

T4

考场上想到了处理移动相交转图上问题搞,但是相交关系比较多,这个思路走不动。

首先得发现一件事,如果用一个方向的移动完全可以移动完,证明考虑找左端点最靠左然后最靠上的,所以左端点一定不会被遮挡,然后如果这条线段被另一些线段遮挡,那么找到遮挡它的线段中左端点最靠左然后最靠上的,依次找下去,左端点递增,所以一定会找到一个不被遮挡的线段,移除它就行。

考虑构造一种方案,不妨要求必须往上移动,对每个方向分别求最早的一次不合法,其它情况可以转坐标系。

考虑求出一个方向上,遮挡限制关系构成的图,首先它一定是个 DAG。我估计我是想不到的,但可能确实平面组合几何问题应该考虑扫描线,由于线段不交,所以扫描线与对应线段交点的相对位置永远不变,用 set 维护扫描线即可,加入时会多出 \(O(1)\) 条边。这样构建出来的图是可以正确描述遮挡关系的,感性理解挺容易的,证明有点难写。

拓扑排序之后第二问做完了,第一问的话,如果出现了拓扑序较大的先被移动了,那么就是不合法的,但是需要注意拓扑序只是一个比较松的限制,还需要加上横坐标区间有交才是两条线段存在先后限制的充要条件,证明是显然的。

需要注意的是,这道题用了 set 去维护边,其中涉及到了浮点数的运算和比较,所以 erase 的时候需要考虑浮点误差。

声明和定义

  • 正常情况下,函数的声明和定义可以分开写,如果函数参数有默认参数,写在声明或定义处都没问题,如果函数为模板参数,那么默认参数只能写在声明处

模板函数

模板函数用于将同一个函数对不同类型生效,一般来说,最好不要用 auto 来捕获,写模板函数才是正确的方式。

模板函数的声明和定义一般不能分开。

隐式指定

如果你想偷懒,就是调用的时候不想写 <T1,T2...>,那么你调用的时候必须能让编译器推断出每个模板类型参数是什么,而且同一种类型不能冲突。

这里需要注意字面量的类型问题。

注意,如果你使用了默认参数,那么调用的时候可能就无法让编译器推断出类型从而出现 CE

1
2
3
4
5
6
7
8
template<typename T1,typename T2>
void add(T1 x,T1 y,T2 len=30);
add(1,1);//CE
add(1,1,30);//OK
template<typename T1,typename T2>
void add(T1 x,T2 y,T2 len=30);
add(1,1.5);//OK
add(1,1.5,30);//CE

类型参数捕获比较阴间的例子,可以发现它会先捕获"简单"的,不太想深究这个:

1
2
3
4
template<typename T1,typename T2>
void add(vector<T1> a,T1 b,T2 c);
add({1.5,3},1.5,5);//ok
add({1.5,3.5},1,5);//CE

显式指定

如果显示指定参数,那么会出现类型强制转换,和正常的函数调用完全相同。

例子就不举了,和正常函数没差别。

指针

常量指针

1
2
const int* p = &a; //指向常量的指针
int* const p = &a; //p 指向的位置不可变

考试

T1

中规中矩的一道题,但是我的做法比较蠢。

考虑最终那些序列可以成为答案的方式没有问题,但是我考虑的条件是对于 \(a_i\),大于等于它的数必须多余 \(n-i+1\) 个。这样需要记录的东西是 \(2\) 维的,然后转移还不是优雅的前缀和形式。实际上可以考虑 \(b_i\ge a_i\) 这个条件,也就是多想一想的事,这样的转移可以写成优雅的前缀和。

像这种转化后考虑条件的题,一定要找一个简易的条件去做。

T2

中规中矩的一道状压题。

我考虑直接爆搜联通块,但是这样的做法不是很优雅。转移时其实仅和当前选了哪些点有关,所以其实可以动态规划,做到 \(3^n\),这种联通块相关的题,一定要考虑联通块之间有没有关系,能不能用动态规划搞定。

T3

中规中矩的一道数据结构优化动态规划的题。

一种比较经典的动态规划优化方式是拿数据结构维护可能的转移,这题本身有着比较优美的性质,可以将对转移代价的贡献拆成两个区间加。

对于修改独立的情况,如果要快速求出答案需要预处理序列,不妨考虑二进制分组。

T4

有意思的二分题。

最大值最小还是可以考虑二分的。

对于这种两者加起来不超过一个数的题,往往可以考虑每个数与 \(\frac{s}{2}\) 的关系。

这种题,显然最终的答案序列是很多个不超过 \(\frac{s}{2}\) 的,中间夹着不超过一个大于 \(\frac{s}{2}\) 的,所以容易证明不超过 \(\frac{s}{2}\) 的数是必选的,如果不选,一定可以调整过去。

然后二分之后需要 check 一下总个数。一个区间中存在合法的大于 \(\frac{s}{2}\) 的数,当且仅当最小值合法。但是如果真的要检查区间最小值个数,那是非常困难的,因为我们需要先得知每个区间的位置,这个位置个数是 \(O(n)\) 的。

所以不妨从合法的数本身的性质考虑,容易发现,合法的数,与它左右两边第一个小于它的数的和一定是小于 \(s\) 的,如果出现相等那么为了避免重复需要设一个第二关键字。

这样就可以做到 \(O(\log)\) check 了,对于两边的边界,可以特判掉。

我们发现需要 check 的东西形如 \([l,r]\)\(\le x\) 的数的个数,这是整体二分擅长处理的东西,所以考虑整体二分,二分的时候将原来的有序组有序的分裂下去,就变成了一维偏序,而且不需要排序(已经有序了)。求左右区间端点也可以类似的做。

对于边界,可以用 ST 表特判,时间复杂度 \(O(n\log n)\)。常数比较大。

整体二分的卡常是有一些技巧的,比如巧妙处理归并,归并的同时分裂数组,通过下标分裂而不是 vector 等。

0%