CSP-S2019部分题解
格雷码
这其实是某年的一道初赛题,但笔者刷初赛的时候嫌太麻烦了没去看.
然鹅它考了,这倒无所谓, (D1T1) 难度还是做得出来的.
通过观察构造方法,我们可以发现,一个有若干位的格雷码一定是从最高层起每一层都去掉一个高位,最后得到空串.
我们还可以发现,一个格雷码在某一层应该去掉的高位是 (0) 还是 (1) 只与它位于该层的前半段还是后半段有关.
于是,我们得到了一个递归的构造方法.
即从最高层开始,判断当前码在前半段还是后半段,然后层数减一,对应地去修改下一层时的编码编号.
需要注意到这样实现的一个重要依据是,每一层都是由上一层加前缀 (0) 正向排列和加前缀 (1) 反向排列拼接而成.
还有一点细节需要注意 (:) 本题的数据范围极大,必须使用 (unsigned : long: long) 才能存下,且不能为了方便把编号向右平移.
总复杂度 (:Theta(log_2{n})) 其中, (n) 是格雷码的位数.
括号树
其实并不是很难的一道题,但因为我太菜了,所以考场上确实没做出来.
注意到,题目要求我们对于树上的每一个点求出它到根的路径上合法的括号子序列的个数的异或和.
显然的一点是一个点的答案一定是继承父亲的答案并加上自己贡献.那么现在的问题就是如何去计算贡献.
对于一个点 (v) , 其父亲记为 (u) , 某个点 (x) 的贡献记为 (con_x) 则有 (con_v=con_u+1) .
这是可以通过观察得到的.贡献的问题也已经解决掉了.
这时候我们发现,一个点的括号能产生贡献,当且仅当它与以它为终点的一段后缀能匹配成一个合法的括号序列.
于是我们就有遇到了一个新问题 (:) 如何在 (dfs) 过程中维护一个历史版本的栈 (?)
主席树维护显然可行,但没有必要,而且会产生大量的空间开销,虽然开得下,但主席树对很多人来说也颇不友好.
我们可以发现,在某一个点向下 (dfs) 时至多会有一个括号被压入栈中,也至多会有一个括号被弹出.
那么对于某一点,我们就记录在该点处栈是否发生变化,是弹出还是压栈,并记录弹出/压栈的元素的是谁,回溯时复原即可.
总复杂度 (:Theta(n)) , 相当优秀.
(Emiya) 家今天的饭
放在 (D2T1) 的位置上,颇具难度,但也无话可说.
我们把每一种烹饪方法看作一个二维矩阵的行,每一种主要食材看作一个二维矩阵的列.
首先考虑题目中的三个限制.第一个限制没什么好说的,就是方案非空.
第二个限制实际上告诉了我们做菜的上界是 (n) 个.即每行中至多选择一个.
第三个限制是本题中最为棘手的一个,也是卡死了很多人的一个限制,如果没有这个限制,说的不客气点,(SB) 题.
第三个限制其实是说在一个合法的方案中每一列至多选 (leftlfloorfrac{k}{2} ight floor) 个,其中 (k) 是该方案的菜品总数.
容易发现,第三个限制中不合法的列,在一个方案中至多只有一列,于是我们可以考虑去容斥.
因为满足前两个限制的方案数非常容易计算,而指定某一列不合法的方案数也不难计算.
于是考虑设 (f_{i,j,k}) 表示对于枚举的当前列 (cur) ,前 (i) 行中当前列选了 (j) 个其他列选了 (k) 个方案数.
其中, (v_{i,cur}) 表示第 (i) 种烹饪方法,第 (cur) 种主要食材能做多少菜,(s_i) 表示第 (i) 行的和.
所有不合法的方案就是(:)
对于枚举的每一列的 $$sum_{i>j}{f_{n,i,j}}$$ 求和.
总方案数可以设 (g_{i,j}) 表示前 (i) 行中选了 (j) 个的方案数.
于是总方案数就是 (:)
这样的总复杂度是 (Theta(mn^3)) 的,能够通过过半的测试点.
这时候考虑优化上面的做法.对于总方案的求法已经够优秀了,所以不需要再额外优化.
只需要考虑如何优化不合法方案的复杂度即可.
我们发现,在转移的时候,我们实际是不关心当前列选的具体个数和其他列选的个数的,我们只需要知道当前列的个数减去其他列的个数的差就能知道某个方案是否是合法/不合法的.
于是考虑差值 (DP) ,状态变为 (f_{i,j}) 表示前 (i) 行,当前列减其他列的差为 (j) 的方案数.
这是最朴素的方程,然而实际的方程却不是这个样子.
因为转化为差值之后就不可避免地会出现负值,而数组下标不允许负值的出现,这时我们可以选择把下标整体右移 (n) ,这样就能保证不出现负下标了.
总复杂度为(:Theta(mn^2)),足以通过本题.