HNOI2019
代码比较长所以直接去LOJ看吧~
鱼(计算几何、向量)
比较套路的内容:枚举\(D\),对于其他所有点按照\(D\)极角排序,按照极角序枚举\(A\),这样垂直于\(AD\)的线也会以极角序旋转,可以使用双指针+map的方式维护合法的\(EF\)点对数量。
相对麻烦的是如何对于每个\(AD\)找到合法的\(BC\)的数量。注意到\(BC\)的限制条件很强,要求\(AD\)是\(BC\)的中垂线,且\(BC\)与直线\(AD\)的交点在线段\(AD\)上。
故预处理所有可能的\(BC\),设\(BC\)中点为\(E\),则计算三元组\((\overrightarrow{x},y,z)\),其中向量\(\overrightarrow{x}\)标识斜率,需要满足向量\(\overrightarrow{x}\)所在直线斜率等于\(BC\)所在直线的斜率、斜率相同直线的\(\overrightarrow{x}\)向量相同;\(y = \overrightarrow{x} ·\overrightarrow{OE}\);\(z= \overrightarrow{x} \times \overrightarrow{OE}\)。
其中\(y\)限制的是\(AD\)在\(BC\)中垂线上:对于任意向量\(\overrightarrow{a},\overrightarrow{b}.\overrightarrow{c}\),如果\(\overrightarrow{b}-\overrightarrow{c} \perp \overrightarrow{a}\),则\(\overrightarrow{a} ·\overrightarrow{b} = \overrightarrow{a} ·\overrightarrow{c}\),证明见点积的几何意义。所以如果\(AD\)为\(BC\)中垂线,那么\(\overrightarrow{OD} · \overrightarrow{x} = \overrightarrow{OE} · \overrightarrow{x}\)。而我们得到了\(\overrightarrow{AD}\)能比较轻松地得到\(\overrightarrow{x}\)和\(\overrightarrow{OD} · \overrightarrow{x}\),所以可以根据三元组前两维确定满足\(AD\)为\(BC\)中垂线的所有\(BC\)。
最后的问题是\(E\)在线段\(AD\)上。根据前面的步骤,\(E\)已经落到直线\(AD\)上,而当某个点\(P\)在直线\(AD\)上向着某一个方向移动时,\(\overrightarrow{x} \times \overrightarrow{OP}\)是单调的,所以如果\(E\)在线段\(AD\)上,则\(\overrightarrow{x} \times \overrightarrow{OE} \in (\overrightarrow{x} \times \overrightarrow{OA} , \overrightarrow{x} \times \overrightarrow{OD})\)。
所以可以预处理上述三元组并排序,对于一个\(AD\)就可以直接二分找到满足条件的\(BC\)的数量。总复杂度\(O(n^2logn)\),注意常数优化。
JOJO(KMP自动机、主席树)
暴力就是把一次的插入一个一个插入KMP。这样插入显然太慢,所以考虑:将一次插入的一大块的字符看做一个新的字符(叫做字符段),连续插入\(i\)个\(b\)字符记做字符段\(b^i\)
我们在以新的字符段为基本元素的字符串上做KMP,KMP的next指向的串是当前前缀的一个border。比如\(a^2b^3a^2b^2a^3b^3\),它的next就是0 0 1 0 1 2。
注意到\(next[4]=0\),这是因为\(b^3 \neq b^2\),所以\(a^2b^3\)不能看做前四个字符段构成的字符串的一个border,这意味着因为\(b^2\)后面不可能插入\(b\),所以把\(b^2\)的next指向\(b^3\)的第二个\(b\)是没有意义的。当然这会使得有答案漏统计,所以要在暴力跳next的过程中统计答案;而\(next[5] = 1\),这是因为加入\(a^3\)时,虽然\(a^3 \neq a^2\),但\(a^2\)是第一个字符段,所以\(a^3\)的后两个\(a\)可以跟\(a^2\)匹配。这种情况也要额外算答案。
这样获得一个比较奇特的KMP。按照这种方法就可以获得第三个部分分。
对于第二个部分分,我们使用KMP自动机:设\(trans[i][ch]\)表示在\(i\)号位置匹配\(ch\)会匹配到的位置。在第\(i\)个位置插入一个字符,就利用\(trans[i-1]\)找到它的next,\(trans[i] = trans[next_i]\),并将\(trans[i - 1]\)更新。
正解就是把第二个部分分和第三个部分分结合起来。注意到将这两个部分结合起来会出现以下的问题:
①字符集大小变为\(26 \times 10^4\),\(trans\)开数组难以接受,所以使用可持久化线段树动态开点维护\(trans\);
②\(trans\)一步到位但是没法统计答案,这个考虑:把总匹配长度的贡献分为两部分,假设\(b^1,b^2,...,b^l\)都被匹配,则一部分是\(\sum\limits_{i=1}^l match_i\),\(match_i\)表示当前插入的\(b^i\)在原串的\(next\)所在字符段之前的所有字符段的总长度,另一部分就是\(\sum\limits_{i=1}^l i\) 。
我们可以用主席树维护每个点每个字符的\(match_i\)。对于在位置\(p\)插入的一个字符段\(b^i\),假设当前插入\(b^j(j>i)\),且通过跳next跳到\(b^i\)前一个字符,那么\(b^1,b^2,b^3,...,b^i\)都会在这里匹配,即\(\forall x \in [1,i]\ match_x = p-1\),这就是一个区间赋值。而插入\(b^j\),会尝试匹配\(b^1,b^2,...,b^j\),所以需要求\(\sum\limits_{i=1}^j match_i\),也就是一个前缀求和。这样我们每一次插入的时候就可以快速求\(match\)并即时维护。求最长匹配长度\(l\)可以维护\(Max[x][i]\)表示从\(x\)匹配字符\(i\)最长可以匹配多少。
③询问要可持久化,其实这个可持久化可以直接离线,把插入的字符插成一个Trie,然后做带撤销的就可以。撤销就只需要把\(trans\)、\(match\)和\(Max\)还原即可。
多边形(模拟、组合)
第一问的答案下界是:\(n-3-\)不是多边形边界且端点均不是\(n\)的边的数量,达到的条件是:每一次都能够找到一个\((a,c)\)旋转,使得\(d=n\)。
可以使用归纳证明法证明一定可以达到答案下界,即对于任意多边形都一定存在一个旋转满足\(d=n\):
\(n=3\)时显然成立;\(n \geq 4\)时,若\(\exists i \in[2,n-2] , (i,n) \in e\),则\((i,n)\)一定不会被旋转,可认为这条边把当前多边形分割成两个小多边形,一个多边形为\(1,2,...,i,n\),另一个为\(i+1,i+2,...,n\),对于这两个多边形均能达到答案下界,所以当前局面能够达到答案下界;
如果不存在这样的点,意味着与\(n\)相连的点只有\(n-1\)与\(1\),所以有边\((1,n-1)\)。考虑多边形\(1,2,...,n-1\),对于每条边,它将多边形分割成两个多边形,保留其中同时存在顶点\(1\)与\(n-1\)的多边形和在其中的边,如是做最后一定会剩下三角形\(1,x,n-1\),而且\(x\)唯一,所以存在旋转\((1,n-1)\)使得旋转后有边\((x,n)\),回到上面的情况。所以当前多边形能够达到答案下界。
考虑第二问。注意到\((x,n)\)边,将原多边形分成了左右两个多边形,假设这两个多边形内经过一次旋转之后与\(n\)相连的点为\(y\)和\(z\),那么\(y\)和\(z\)连向\(n\)就会在\(x\)之后。不妨将\(y\)和\(z\)作为\(x\)的左右儿子,那么原来的操作序列就变为了一个森林,第二问就相当于找一个序列满足对于所有\(x\),\(fa_x\)在\(x\)的前面。
不难发现树上每个点的贡献是独立的,对于点\(x\),它对答案的贡献为\(\binom{sz_x - 1}{sz_{lson}}\),即它可以把它的两个后继的序列在不改变它们内部的顺序的情况下随意组合。整体的答案就是把所有点\(x\)的贡献乘起来再乘上把整个森林合并起来的方案数
考虑修改。如果修改的是一棵树的根,那么只有把森林合并起来的部分的贡献会有变化,否则这一次旋转对答案的影响就是某一个点的左儿子用\(Splay\)的一次\(rotate\)旋到父亲之上(注意这里一定是左儿子,如果选择的边对应某个点的右儿子,那么这条边在\((a,b,c,d)\)四边形中一定会对应\((b,d)\)边而不是\((a,c)\)边),贡献也只会有这些部分变化。把之前的贡献除掉、乘上新的贡献就可以了。
不懂的部分建议自行画图理解。
校园旅行(二分图、BFS)
一个比较显然的暴力:设\(f_{i,j}\)表示路径两端在\(i\)点和\(j\)点时是否存在回文路径,直接BFS维护转移,复杂度为\(O(\sum\limits_i \sum\limits_j d_id_j) = O(m^2)\)
看起来似乎没有更好的方法得到答案,所以考虑优化边的数量。
设\(ij\)边表示一端权值为\(i\),一端权值为\(j\)的边。将所有\(01\)边加入,原图会构成若干连通块,考虑仅保留每个连通块的一棵生成树。假设一条路径中,两端点中一端最短需要经过\(x\)次\(01\)边,另一端最短需要经过\(y\)次\(01\)边(\(x \equiv y \mod 2\),因为可以在\(01\)边上反复横跳),那么\(x\)和\(y\)的值可能会增加,增加数量可能不同。但是因为\(01\)边构成的连通块一定是一个二分图,所以在保留一棵生成树之后,一端一定会需要经过\(x + 2a(a\geq0)\)次\(01\)边,另一端需要经过\(y+2b(b\geq0)\)次\(01\)边,而加上\(2\)的倍数不会改变奇偶性,所以原来存在的路径在只保留一棵生成树之后仍然存在。
对于\(00\)边和\(11\)边我们也按照上述方法去做,但是注意到:如果某一个连通块内存在奇环,则可以通过绕奇环改变经过边数的奇偶性,但是生成树是一个二分图,所以如果某个连通块内出现了奇环,给这个连通块内的一个点加上自环,就和原连通块等价。
按照上述方式边数降低为\(O(n)\)级别,BFS复杂度变为\(O(n^2)\)。
白兔之舞(矩阵快速幂、任意模数NTT)
我们要求的就是\((E+Wx)^L\)在\(k\)进制循环卷积意义下的每一项的系数。
因为\(k \mid P-1\),所以\(w_k\)有意义。故先DFT求出点值表示,\(x=w_k^i\)之后就是一个矩阵快速幂,可以\(O(3^3klogL)\)地求出。
然后IDFT求出它的系数表示。IDFT式子是
\(f_i = \frac{1}{k}\sum\limits_{j=0}^{k-1}g_jw^{-ij}\)
注意到除了\(g\)以外其他都是整数,所以\(g_j\)可以是将\(w_k^j\)代入上式之后求得的点值表示矩阵,也可以直接取这个矩阵第\(X\)行第\(Y\)列的值。
然后可以多项式多点求值求\(w^{0},w^{-1},...,w^{-(k-1)}\)的值,但是因为要拆系数FFT所以常数肯定大到跑不过
考虑一个经典的拆\(ij\)的方式:\(ij = \frac{(i+j)^2}{2} - \frac{i^2}{2} - \frac{j^2}{2}\),但是\(i,j\)有可能是奇数,又无法确定\(w\)是否有二次剩余,所以换一种:\(-ij = \binom{j-i}{2} - \binom{j}{2} - \binom{i+1}{2}\),组合数都是整数,这样上式变为
\(f_i = \frac{1}{kw^\binom{i+1}{2}}\sum\limits_{j=0}^{k-1} \frac{g_j}{w^\binom{j}{2}} w^{\binom{j-i}{2}}\)
用拆系数FFT把后面的求和卷起来就可以求出\(f_i\)。
序列(贪心、可持久化单调栈)
这题结论很多,证明可以看官方题解或者感性理解,比较重要的结论有:
①最优方案一定是将原序列划分为若干等值块,每一个等值块的\(B\)相同,且一定是它们权值的平均数。
②贪心的正确性:用一个栈维护等值块,每一次新加入一个数,先把它自己看做一个等值块,然后不断考虑栈顶,如果栈顶的\(B\)比当前加入的等值块的\(B\)要大就把这两个块合并。
③把某个序列划分为左右两半,那么最优方案中,整个序列的划分点(划分点即满足\(i\)和\(i+1\)不在同一个等值块内的\(i\))一定是左右两半最优划分的划分点的并的子集。这意味着某个序列的最优划分可以看做将这个序列划分为两半,左边取最优划分,右边取最优划分,然后把左边的一段后缀和右边的一段前缀合并到一起,并且合并方式唯一。
④对于上面的一段后缀和一段前缀的合并,存在一种先二分右端点、二分右端点的过程中二分左端点进行check的单次\(O(log^2n)\)的做法
那么对于一次修改,可以认为把\(i\)单独看做一块,\(1\)到\(i-1\)看做一块,\(i+1\)到\(n\)看做一块,将\(i\)的权值修改之后三个块重新合并,还是可以用上面④的那种二分套二分的方法。至于如何求\(1\)到\(i-1\)和\(i+1\)到\(n\)的最优划分,直接用可持久化单调栈,预处理出所有前缀后缀的最优划分方案即可。复杂度\(O(nlogn+mlog^2n)\)