2016 Multi-University Training Contest 7 solutions BY SYSU

Ants

首先求出每个点的最近点. 可以直接对所有点构造kd树,然后在kd树上查询除本身以外的最近点,因为对所有点都求一次,所以不用担心退化. 也可以用分治做,同样是O(NlogN)的复杂度. 方法是每次用一条竖直线将平面划分成左右两半(平行线划分上下两半也可以),对两半中的点分别递归求每个点的最近点. 然后对左半边的一个点p,如果以它到左半边的最近点的距离为半径画出来的圆和右半边相交,则需要判断右半边是否有距离p更近的点. 我们求出这些圆和竖直平分线的交点,并将右半边的点投影到竖直平分线上,显然对于右边的点q,只有当其落在左边的点p画出的圆和平分线的两个交点之间时,我们才需要判断点q是否离p更近. 另一方面可以证明左边画出的圆,不可能出现7个或7个以上的圆有公共点,因此对于右边的每个点,最多只需要和左边的6个点进行判断. 因此我们可以用O(N)的时间扫描平分线上的圆交点和投影点即可. 不需要每次都对平分线上的这些点进行O(NlogN)的排序,只需要分治前对所有点按y值排好序,后面划分的时候维护好这个序就可以. 对于右半边的点,求其在左半边的最近点也是类似的.

接下来是处理询问,通过反证法容易证明以下两个性质:

每个点往其最近点连一条线段,所有这些线段不会相交于除端点外的点.
每个点往其最近点连一条有向边,在得到的有向图中,不会出现点数大于等于3的环.
根据以上两个性质,我们可知任何一只蚂蚁出发后都会陷入一个点数为2的环中,因而当且仅当它们陷入同个环时会相遇. 是否陷入同个环,只需要判断它们是否在同一个连通分量(将性质2中的有向边看作无向边得到的连通分量)中即可,这个很容易用并查集或BFS来预处理使得后面可以O(1)回答每个查询.

Balls and Boxes

\(E[V] = E[\frac{\sum_{i=1}^{m} (X_i - \bar X)^2}{m}]=E[(X_i - \bar X)^2] = E[X_i^2 - 2X_i\bar X +\bar X^2]\)

\(= E[X_i^2] - 2\bar XE[X_i] + E[\bar X^2] = E[X_i^2] - 2\bar X^2 + \bar X^2 = E[X_i^2] - \frac{n^2}{m^2}\)

所以关键是要求出\(E[X_i^2]\). 我们用随机变量\(Y_j\)来表示第j个球是否在第i个盒子中,如果在则\(Y_j = 1\),否则\(Y_j = 0\). 于是

\(E[X_i^2] = E[(\sum_{j=1}^{n} Y_j)^2] = E[\sum_{j=1}^{n} Y_j^2] + 2E[\sum_{j=1}^{n} \sum_{k=1,k\neq j}^{n} Y_jY_k] = nE[Y_j^2] + n(n-1)E[Y_jY_k]\)

\(=\frac{n}{m}+\frac{n(n-1)}{m^2}\)

因此,

\(E[V] = \frac{n}{m} + \frac{n(n-1)}{m^2} - \frac{n^2}{m^2} = \frac{n(m-1)}{m^2}\)

Colosseo

判断YES或NO的时候拓扑排序或者暴力O(N^2)判断都可以. 接下来对于T2中的每个点,只需要在T1中扫一遍就可以判断出是否可以放进T1,如果不能放进去就直接丢掉,如果可以就确定放在哪个位置. 注意因为是个竞赛图,所以这个位置是唯一的. 给T2剩下的点按在T2中的拓扑顺序(因为是竞赛图,所以这个顺序也是唯一的)标上它们在T1中的位置值,对得到的这个位置值数列求最长上升子序列的长度就是答案. 总复杂度O(N^2).

Distance

不难发现d(a, b) = f(a/gcd(a, b)) + f(b/gcd(a,b)),其中f(x)表示x的质因子个数. 因而当遇到操作Q x时,我们只需要枚举x的每个约数y,看属于当前集合的y的所有倍数z中f(z/y)的最小值为多少. 为了快速求出这个最小值,我们用C[y][s]表示当前集合中y的所有倍数z中使得f(z/y)=s的z的数量. 因为s的值不会超过20,所以可以用位压缩的方法,用D[y]表示y的倍数中哪些s值出现了,这样查询最小的s值可以通过位运算快速求出(因为时限是标程的3倍,所以也不会特意卡掉其它方法). 插入和删除x时同样可以通过枚举x约数的方法来更新C[y][s]和D[y]的值. 设M表示元素的最大值,因为1到M所有约数的数量是O(MlogM)的,所以算法的时间和空间复杂度也都是O(MlogM)的. 又因为操作数少于M,所以实际情况还会更好一些.

Elegant Construction

将顶点按能到达的点数从小到大排序,排好序之后每个点只能往前面的点连边. 因而如果存在一个排在第i位的点,要求到达的点数大于i-1,则不可行;否则就可以按照上述方法构造出图. 复杂度O(N^2).

Find the Period

这题用到一个称为基本子串字典(Basic Factor Dictionary)的数据结构. 所谓基本子串是指字符串S中所有长度为2k的子串,基本子串字典就是将基本子串按字典序组织起来,对于那些本质相等的子串,需要记录下它们多个出现的位置. 构造这个字典的方法就是将倍增构造后缀数组的过程保存下来. 对S进行预处理的时候构造出S的基本子串字典,这一步时间和空间复杂度都是O(NlogN).

查询最小周期的时候,注意到一个长度为N的字符串以p为周期,等价于字符串长度为N-p的前缀和后缀相等. 对于一个查询,为了找到子串S[L..R]中最长的一个真前缀同时也是S[L..R]的后缀,我们用“倍缩”的方法. 先对于可能的最大的k,查询从S[L..L+ 2k-1]的基本子串在S[L..R]出现的位置,这个可以在上述的字典中二分查找,设查询到的位置集合为A. 类似地,我们也查询S[R-2k+1..R]在S[L..R]出现的位置,设查询到的位置集合为B. 如果A和B中分别存在值a和b,使得a-L和R-b相等,则上述要查找的真前缀存在. 另外,当2k不小于S[L..R]长度的一半时,A中的值和B中的值都是等差数列,所以不用担心A和B中有多个值导致算法复杂度增加. 接下来我们继续在S[L..R]中是否有长度在2k-1和2k之间的符合上述要求的前缀,这个方法和前面的过程是类似的. 因而每个查询的复杂度为O(log2N).

Golden Week

用f[i][j]表示当从首都到城市i的路径花费为j时,考虑以i为根的子树中的点作为目的城市的旅行者的最大花费. 因为我们只需要考虑j值等于0或等于某位旅行者预算的情况,所以dp的状态数是O(NM)的. 状态转移方程如下:

\(f(i,j) = j * n(i,j) + \sum_{the\ each\ sons\ k\ of\ i } max_{j'\geq j}f(k,j')\)

其中n(i, j)表示在以i作为目标城市且预算不小于j的旅行者数量. 上式的max部分可以通过简单的递推对每个j值记录下对应的最优j’值,使得每次转移的时候不需要每次重新枚举j’的值,这样可以将算法的总复杂度降到O(NM). f(1, 0)就是最终的答案,定价的方案容易由状态转移的最优决策构造出来.

Hearthstone

这题其实有O(2^M)的做法. 方法用f[i][j]表示A类牌和B类牌分别抽到i张和j张,且抽牌结束前保证i>=j的方案数,这个数组可以用O(n^2)的dp预处理得到. 接下来枚举B类牌的每个子集,如果这个子集之和不小于P,用k表示子集的1的个数,将方案总数加上取到这个集合刚好A类卡片比B类卡片少一(过程结束)的方案数:f[k-1][k] * C(n, k - 1) * (k - 1)! * k! * (n + m – 2*k + 1)! . 如果子集包含了所有的B类卡片,则还需要再加上另一类取牌结束的情况,也就是取完所有牌,此时应加上的方案数为f[n][m] * n! * m! . 最后的总方案数除以(n+m)!就是答案.

这题因为定义为比较简单的题目,所以M最大只到20,这样O(N*2^M)状态压缩dp的做法也是可以通过.

Joint Stacks

比较简单巧妙的一个做法是引入一个新的栈C,每次合并的时候就把A和B合并到C上,然后把A和B都清空. push还是按正常做,pop注意当遇到要pop的栈为空时,因为题目保证不会对空栈进行pop操作,所以这时应直接改为对C栈进行pop操作. 这样做因为保证每个元素最多只在一次合并中被处理到,pop和push操作当然也是每个元素只做一次,所以总复杂度是O(N)的. 另一种做法是用链表来直接模拟,复杂度也是O(N),但代码量稍大一些.

Ice Walls

第一步构图,第二步求最短路.

构图的时候,求每个点u到其它点穿过多少线段用极角排序和旋转扫描的方法,每扫描到一条线段的起点就将这条线段加入当前扫描线经过的线段集合,每扫描到一条线段的终点就将这条线段从当前集合删去,离散化后用一个树状数组来维护这个集合就可以方便地统计出当扫到一个点v时v到u之间有多少条线段在它们之间. 为编程方便可以将水平线和竖直线分开求. 构图的复杂度O(N^2logN).

求最短路可以用Dijkstra,复杂度O(N^2). 总复杂度O(N^2logN). 本题即便线段没有平行于坐标轴的要求也可以用类似方法做.

Knights

第N个骑士活到最后的情况就是他在往左走的过程中一路打败迎面走来的往右走的骑士. 考虑用f(i, j)表示前i个骑士有j个往右边走的概率,分两种情况:

1) 第i个骑士初始是往左边走,于是

\(f(i,j) = \sum_{k=j}^{i-1}f(i-1,k)*(\frac{1}{2})^{k-j+1}\)

这是因为前i-1个人中有k个往右走,此时要使前i个人中有j个往右走,第i个人必须要赢下k-j场,再输掉一场. 由上式可得

\(f(i,j) = \frac{f(i,j+1)+f(i-1,j)}{2}\)

2) 第i个骑士初始是往右边走,于是f(i, j) = f(i-1, j-1). 这是因为在前面基础上加多一个往右走的.

最终结果为\(\sum_{j=1}^{n-1} f(n-1,j)*\frac{1}{2}^j\). 复杂度O(N^2).

Lights

对于每个点(x, y),正左方最近的点记为(x’, y),正上(下)方最近的点记为(x, y’),如果以这3个点为顶点的矩形内部存在有其它的点(x’’, y’’),则从(x, y)到(x’’, y’’)不存在good path. 反之,如果整个图中都不存在这种情况,则任意两点间都有good path. 因而可以用扫描线和可持久化线段树来做,从左往右扫描,每个x值对应线段树的一个版本,每遇到一个点(x, y)和它正上(下)方最近的点(x, y’)就在当前版本的线段树和版本为x’的线段树上查询y和y’之间的点数,如果这两个值不相等,则意味着存在上述的点(x’’,y’’). 对于每条扫描线处理完查询之后再将点插入到当前版本的线段树中. 复杂度O(NlogN). 用一般的线段树或树状数组也可以做,但代码量可能反而更大一些.

posted @ 2016-08-16 19:59  多校博客  阅读(795)  评论(0编辑  收藏  举报