HNOI 2019 简要题解
没想到自己竟也能有机会写下这篇题解呢。
Day1T1 鱼
枚举(AD)两点后发现(BC)与(EF)相对独立,因此只需要计算合法的(BC)对数与(EF)对数,相乘即可。
先考虑计算(EF)的对数。确定(AD)后,满足条件的(EF)对数即为在某个半平面内与(D)点距离相等的点对数目。枚举(D)后若乱序枚举(A),则需要再(O(n))地处理(A)确定的半平面内的合法点对数目。可以按照极角序枚举(A),并用单点指针确定半平面的范围,即可在总计(O(n^2))的时间复杂度内解决这部分问题。
再考虑计算(BC)的对数。发现(AD)对(BC)的限制很强,直接限定了(BC)的斜率(或言之方向向量,因为有可能斜率不存在)以及中点坐标,因此可以考虑直接(O(n^2))预处理出所有(BC)点对,每次查询时只需要在对应的值上二分找出范围限制即可。总时间复杂度(O(n^2log n))。
具体实现上,根据“与向量((a,b))点积相同的点在一条垂直向量((a,b))的直线上,与向量((a,b))叉积相同的点在一条平行向量((a,b))的直线上”,我们可以直接通过点积与叉积确定(BC)的具体位置。
Day1T2 JOJO
考虑解决没有(2)操作的子问题。
定义一个二元组((x,c))表示有连续的(x)个(c)字符。考虑对一个由二元组组成的字符串求(next)数组。可以发现若(next)需要跨越某个二元组,则必须满足前后缀对应二元组相等(起始位置除外),这里的二元组相等定义为每一维均相等。起始位置二元组的字符要求相等而长度不要求相等,但必须大于等于前缀的对应位置。
现在考虑加入一个二元组((x,c))后如何快速求得答案。沿(next)链往前跳时,每次遇到字符为(c)的二元组时,都能够为新加入的((x,c))中的某一段区间找到其(next),其贡献为一个公差为(1)的等差数列。特别的,新加入的((x,c))的一段后缀可能只能与首位二元组匹配,此时的贡献是首位二元组长度的若干倍。
这样就解决了没有(2)操作的子问题。当出现(2)操作时,常规的求解(next)数组的做法就不再适用了——求解(next)数组的时间复杂度是均摊的。因此,我们需要对求(next)的过程进行优化。首先离线建树,在自上而下(dfs)的过程中,维护每条(next)链上的(kmp)自动机(即(f_{i,j,k})表示在(i)状态下加入一个二元组((j,k))后(next)会指向哪里,同时即(g_{i,j,k})表示对答案产生的贡献),每次修改是复制前一位的(f)数组,并将(f_{i,x,c})修改为(i),将(g_{i,x,1...c})修改为一个首项为前(i-1)个二元组总长度(+1),公差为(1)的等差数列,查询即查询前缀和。用可持久化线段树实现即可,复杂度(O(nlog n))。
具体实现上,可以对(g_{i,j,k})全体减下标,这样修改就变成了区间赋值。最后再加上下标的等差数列即可。
Day1T3 多边形
最优策略下每次旋转操作一定满足(d=n),因此总步数就等于初始局面下两端点均不是(n)的边的条数(边界上的边不计入)。
将初始局面下所有与(n)相邻的点(包括(1)和(n-1))排序,这样游戏终止的条件就变成了将。对于排序后相邻的两点(l,r),必然存在一条边((l,r)),否则原图就不是一个合法的三角剖分。此时若(l+1=r)那么这条边没有操作空间,可以直接忽视,否则在((l,r))中必然存在一个点(p)使得((l,p),(p,r))均有边,这样对((l,r))进行一次旋转操作后即可得到边((p,n)),同时相等于在((l,r))中间插入了一个数(p)。接下来区间((l,p))与((p,r))独立,方案数即可分别计算。
不难发现上述过程中隐含的树形结构。以初始局面下所有(l+1<r)的边((l,r))作为树根,即可得到一片二叉树森林,其中每个节点必须在其父亲操作之后才能操作。这样方案数边不难计算:合并两棵子树的方案数为组合数。
对于一次任意的旋转操作,可以发现要么是在最优策略下操作了一步使得最优步数减(1),在树形结构上的表现为删除某棵二叉树的根,将其左右子树分别作为两棵新的二叉树,要么是对某个点(而且一定是左儿子)进行了一次(rotate)操作,不改变最优步数。两种情况的方案数均可以通过计算组合数逆元求出。
时间复杂度(O(n+m))或(O((n+m)log n))。
Day2T1 校园旅行
有一个(O(m^2))的暴力(dp)做法,记(f_{i,j})表示是否存在一条从(i)到(j)的回文路径,转移即分别枚举(i,j)的相邻点转移即可。
这个(dp)可以进行一些小优化,额外记(g_{i,j})表示(i)是否存在一个相邻点(i')满足(f_{i',j}=1),这样转移就只需要枚举一遍,复杂度(O(nm))。
(不知道怎么想的)考虑减少边数。对于所有连接同色点的边,每一个连通块内只需要保留一棵生成树即可得到与原图相同的结果。特别的,若该连通块不是二分图,则需要添加一条自环。对于所有连接异色点的边做同样的事情,这样总边数就降到了(O(n))级别,再套用上面的做法即可。
Day2T2 白兔之舞
考虑枚举实际走了(i)步,会发现走(i)步的方案数恰好就是(inom Li)。于是我们要求的答案就是:
随手单位根反演一下
令(a_j=((omega_k^jA+I)^L)_{x,y}),然后原式(差不多)就变成了
这不就是裸的(FFT)?
对于(k=2^n)的Case,直接(FFT)即可,复杂度(O(3^3klog L+klog k)),期望得分(60)分。
出考场后:
接着lun给我丢了份16年myy的集训队论文。
所以接下来我们做剩下的(40)分。
考虑(jt=frac{(j+t)^2-j^2-t^2}{2}),于是原式就变成了
移项后就可以直接卷积了!
然而此时你会发现(frac{x^2}{2})这东西有可能不是整数,也即需要用到(2k)次单位根,而和(du)善(liu)的lun一定也会造数据来卡你。
不过不要慌,转念一想,你会发现还有一种转化的方法:
(jt=inom{j+t}{2}-inom j2-inom t2)
于是愉快地套用上式就行啦。
(MTT)实现卷积即可,复杂度依旧为(O(3^3klog L+klog k))。
Day2T3 序列
证明?看官方题解吧
第一个结论是答案的构造方式:从左到右依次加数,当发现当前的数比前边的数字小的时候就将整段替换成所有数的加权平均值,用一个单调栈即可维护。这同时也是(50)分(O(nm))的做法。
以下所有下标(除了(x))均为单调栈的下标而非原序列的下标。
现在对(x)位置进行了权值的修改。考虑最终答案里(x)位置的所在区间(段),设之为([L_0,R_0]),则([1...L_0-1],[R_0+1...n'])在最终答案中的结构与单独考虑之的结构是一样的。也就是说只需要维护出所有前后缀的单调栈,再想办法对于每次询问找出(L_0,R_0)就行了。
考虑枚举一个(R)检查其是否可以作为(R_0),对这个(R)找出以其为右端点向左合并时会合并到的位置,记其为(L),则若([L,R])的平均数小于(R+1)的权值则这个(R)就是我们要找的(R_0)。
枚举(R)可以改为用二分实现,同时对于一个给定的(R)我们可以通过在线段树上二分的方式做到(O(log n))地找到其对应的(L)。因此总复杂度(O(nlog n+mlog^2n))。