题解-2019省选题题解集合
这是一篇面向自己的文章…正在补坑…部分题目太水就不放代码了
省份 | D1T1 | D1T2 | D1T3 | D2T1 | D2T2 | D2T3 |
---|---|---|---|---|---|---|
\(\mathrm {BJOI}\) | X | X | X | X | ||
\(\mathrm {GZOI}\) | X | X | X | X | ||
\(\mathrm {HNOI}\) | X | X | ||||
\(\mathrm {JSOI}\) | N/A | N/A | N/A | X | ||
\(\mathrm {SDOI}\) | X | |||||
\(\mathrm {SNOI}\) | X | X | X | |||
\(\mathrm {TJOI}\) | X | X | X | X | ||
\(\mathrm {ZJOI}\) | X | X | ||||
\(12\)省联考 | X | X | X |
BJOI2019
D1T1 奥术神杖
明显可以二分答案 \(a\),\(a\leq \sqrt[c]{\prod w_i}\Leftrightarrow a^c\leq \prod w_i\),设 \(w'=\frac wa\),则可以在咒语构建出的 AC自动机 上 dp 了
D2T1 排兵布阵
显然对于每一座城堡,只有等于某个人+1时才有可能成为最优策略,做个背包 \(O(nms)\)
D2T2 光线
D2T3 删数
设 \(b_i\) 为值为 \(i\) 的数的个数,发现 \(\forall i\in[1,n]\),若不存在 \(j\) 使得 \(i\in(j-b_j,j]\),则位置 \(i\) 需要一次“修改”,则答案就是统计位置 \([1,n]\) 共有多少位置需要“修改”
线段树维护,可以使用扫描线线段树的技巧(加tag和删tag一一对应,不用下推标记)。单点修改相当于区间修改几次;整体加减一相当于将区间左右移,这个可以记录平移偏量,对进出右边界的格子进行修改
Code
GZOI2019
D1T1 与或和
位运算的题明显按位拆开考虑,对于每一位相当于是问有多少个全 \(0/1\) 矩阵:枚举矩阵的右下角,考虑左上角关于横坐标是一个单调栈,在扫描每一行的时候维护这个单调栈即可 \(O(n^2)\)
总复杂度 \(O(31n^2)\)
D1T3 特技飞行
D2T2 旅行者
二进制分组:进行 \(\log n\) 次分组,第 \(i\) 次按照关键点编号的二进制编码下第 \(i-1\) 位是否为一分类,每次求出从一个集合到另一个集合的最短路。总复杂度 \(O((n+m)\log^2n)\),已然可以通过此题
直接暴力:先跑一遍最短路,将边反向后再跑一次最短路,考虑每条边的贡献(两端点最近关键点的距离,若是同一个点则不予统计)。总复杂度 \(O((n+m)\log n)\)
D2T3 旧词
做过 LNOI2014 lca 的可以秒切,那题大概就是这题去掉上头那个 \(k\) 次幂,下面是那题的题解:
可以将一个点的深度转换成该点到根节点的路径上的点个数,那么考虑将这个点的深度 \(dep\) 贡献平摊到这个点到根路径上的总共 \(dep\) 个点上去,每个点贡献为一,那么查询这个点的权值就是查到该点到根的权值和
那么 \(\sum_{i=l}^rdep[lca(i,z)]\) 的询问就是:
将\(l\)到\(r\)这些点到根的路径上的点的点权全部加一(每个点都要加一次),最后询问\(z\)到根路径上的权值和,离线扫描即可。复杂度 \(O((n+m)\log^2n)\)
回到 GZOI 这题来,这题就只要在每个点上预先设好 \(dep^k\) 的权值,加 \(1\) 变为加对应的点的权值,线段树也支持。复杂度不变依旧是 \(O((n+m)\log^2n)\)
HNOI2019
D1T3 多边形
明显这个多边形的终态是点 \(n\) 连出 \(n-3\) 条边,为了达到最优解,则每次一定会连一条边到 \(n\),同时也一定会存在这样的一次操作,第一问得解
考虑第二问。模拟上头的贪心,发现每次可以将当前区段分为两段分治求解,即一棵树结构,答案就是这棵树不同的拓扑序数,拓扑序数的统计可以在每个节点处用组合数合并儿子们,做到这里就有 \(65\) 分了
再考虑合并儿子的式子,可以由 \(\prod \binom {sz_1+sz_2}{sz_1}\) 的形式转成 \(\frac {sz_x!}{\prod_{x\rightarrow v}sz_v!}\),则计算了根节点后,其余节点的贡献是 \(\prod \frac 1{sz_x}\) 的形式,而一条边 \((x,y)\) 在树上对应节点的子树大小为 \(|x-y|-1\)
至于怎么快速进行一次旋转,对于每个点维护一个出边集合,找前驱后继即可
Code
D2T1 校园旅行
这题我在省选考场上被卡栈卡掉了 \(40pts\),而且出题人本来打算给我的做法 \(70pts\) 的,然后就成B类垫底了 (⊙v⊙)
但是这题的 \(70pts\) 做法和 \(100pts\) 做法是两个方向 (∪_∪),myy的做法感觉很妙
\(30pts\) 考虑一条回文路径在两端加上一对相同的点也是回文路径,所以从一个点或者一条同色边开始扩展,记忆化即可 \(O(n^2+m^2)\)
\(70pts\) 也差不多,只是在转移时两端不要同时转移,先转移一边再转移另一边,可以做到 \(O(n^2+nm)\),但是实际在考场上测得只有 \(30pts\),数据只放过了 \(40pts\) 然后就被卡栈惹
正解依旧使用 \(30pts\) 做法,只不过将边的数量缩小到 \(O(n)\) 级别:
- 将边分为三类:两端都是 \(0\);两端都是 \(1\);一端是 \(0\) 一端是 \(1\)
- 将三类边每一类单独建图,对于每一个联通块而言,若为二分图则保留一棵生成树,否则在某一个点上加上一条自环
正确性不好说明,自己去理解比较好:对于任意一条原图上的回文路径,可能会因为上面做法中只保留一棵生成树而毁坏,但连通性还是可以保证的,而且能经过保留的生成树找到对应同奇偶的替代方案(若这个图不是二分图就要加自环 是因为原图中可以改变这个点的奇偶性,而新图中可以依据这条自环改变奇偶性)
Code
JSOI2019
D2T2 神经网络
反正这些细节我是想不出来
先不考虑回路的事。
首先明确这个图中的哈密顿路相当于将这个森林中每一棵树都剖成若干段,将这些段排成一列,任意相邻两段不能排成一列
可以发现这个东西可以容斥,更具体的,可以算出每棵树的容斥系数,最后相乘:
设 \(s[i][j]\) 表示将 \(i\) 个元素排成 \(j\) 个排列的方案数,可以先考虑划分成 \(j\) 个集合的方案数,最后再乘 \(j!\)
设 \(g[i]\) 表示将这棵树划分为 \(i\) 条链的方案数,设 \(f[i][j][0/1/2]\) 来树形Dp可得
则这棵树剖出 \(i\) 段的容斥系数为 \(F_i=\sum_{j=i}^n(-1)^{j-i}g[j] \cdot s[j][i]\),将这个乘到总的序列里去(需要根据位置乘组合)
再回来考虑回路的问题,只需要限定第一棵树的 \(1\) 号点所在链为起点,且最后一条链不能在第一棵树上
至于 \(s\) 数组,考虑先用 \(s'[i][j]=\sum_{k=j-1}^{i-1}s'[k][j-1]\) 转移,再 \(s[i][j]=s'[i][j]\cdot j!\)(实际上可以发现 \(s[i][j]=\binom {i-1}{j-1}\cdot j!\))
以及 \(f\) 数组:
\[f'[x][i+j][0] += f[x][i][0] \cdot \sum f[v][j][0/1/2] \]\[f'[x][i+j-1][1] += f[x][i][0] \cdot f[v][j][0]\cdot 2\\ f'[x][i+j-1][1] += f[x][i][0] \cdot f[v][j][1]\\ f'[x][i+j][1] += f[x][i][1] \cdot \sum f[v][j][0/1/2] \]\[f'[x][i+j-1][2] += f[x][i][1] \cdot f[v][j][0]\\ f'[x][i+j-1][2] += f[x][i][1] \cdot f[v][j][1] \cdot \frac 12\\ f'[x][i+j][2] += f[x][i][2] \cdot \sum f[v][j][0/1/2] \]Code
SDOI2019
D2T2 移动金币
首先可以将每枚金币看做一堆石子,这堆石子的个数就是这枚金币与其左端最近金币的距离,每次可以选择一个第 \(i\) 堆的若干石子放入第 \(i+1\) 堆。
这是反过来就是一个“阶梯nim”游戏,一个结论是若奇数位的石子数异或和为零则先手必败,其他情况先手必胜
则 \(50pts\) 的子任务就可以暴力Dp了。
对于满分子任务,考虑设 \(f[i][j]\) 表示考虑由大到小的前 \(i\) 个二进制位异或和都为零,且目前这些位上的和为 \(j\) 的方案数。转移考虑对于当前位,在奇数位上放了偶数个 \(1\),并考虑偶数位上的随意防止,组合数转移即可。详见代码
Code
SNOI2019
D1T1 字符串
看到有人用 \(O(n\log n)\) 的做法,其实可以 \(O(n)\)
明显可以将相邻的相同字符合并,先处理完相邻字符都不一样的情况,最后在输出时一起输出。比如 aaabb
,可以先将 aaa
和 bb
分别合并,得到 ab
,排序可得答案为 4 1
,再将刚被合并的相邻相同字符放进去,得到 (4) 5 (1) 2 3
(其中打括号的为合并后的答案,无括号的即刚加入字符)
那么只需考虑相邻字符不同的情况惹。对于比较 \(s_i,s_j(i
Code
D1T2 数论
枚举 \(A\) 集合中的每一个值,其对应的 \(x\) 满足\(a+Pt\equiv x \pmod Q\),左部在同余情况下是 \((P,Q)\) 个环,统计环的个数与多余部分即可
Code
D1T3 通信
建出费用流模型:拆点 \(i,i'\):
- \(\forall i,s\rightarrow i,i'\rightarrow t\)
- \(\forall i < j,i\rightarrow j'\)
这样有 \(80pts\)
两种方法优化建边:
- 主席树:绝对值分两个方向用主席树优化建边;
- CDQ:对于每一层建出双向导通的数轴,cdq的一端连入,另一端连出
最后需要zkw费用流
Code
TJOI2019
D1T1 甲苯先生的字符串
矩乘快速幂模板题
D1T2 甲苯先生的滚榜
splay模板题
D2T1 大中锋的游乐场
刚看题只看到了 \(n\leq 10^4\),想了两分钟,然后看到了 \(k\leq 10\),然后就变成了——拆点最短路模板题
点数 \(2nk\),边数 \(mk\),复杂度 \(O(T(n+m)k\log nk)\),但由于大数据不超过两个,可过
D2T2 甲苯先生和大中锋的字符串
sam or sa 模板题
ZJOI2019
D1T2 线段树
设已修改了 \(c\) 次,考虑其问题本质是对于 \(c\) 次修改,每一次修改选与不选共 \(2^c\) 棵线段树的答案和。变相考虑在 \(2^c\) 棵树中有 tag 的期望,最后乘上 \(2^c\) 即为答案
进一步的,统计答案是枚举线段树的每一个节点,求其有tag的期望和,最后乘 \(2^c\),所以目标是维护每个节点有 tag 的期望
在草稿纸上对线段树的区间修改进行模拟,发现修改路径上的 tag 全部会消失,在修改路径的末端打上一个 tag,而若修改路径上的一个点 \(x\) 有 tag,则 \(x\) 以下的路径旁的节点都会因此打上 tag
对每个节点维护一个值 \(f\),表示这个节点有tag的期望;而为了维护对修改路径旁节点的影响,可以考虑维护一个 \(g\) 值,表示在线段树上这个点到根这条路径上至少有一个 tag 的期望
整理一下,得到:
- 对于修改路径上的节点:\(f=\frac 12f,g=\frac 12g\)(一半的情况中不变,一半的情况中 tag 消失)
- 对于修改路径旁的节点:\(f=\frac {f+g}2\)(一半的情况中不变,一半的情况中依赖于路径上是否有 tag)
- 对于修改路径的末端:\(f=\frac {f+1}2\)(一半的情况中不变,一半的情况中一定存在 tag)
- 对于修改路径末端的子树:\(g=\frac {g+1}2\)(同理……)
线段树维护即可(对于多次 \(g=\frac {g+1}2\) 的标记,若标记有 \(k\) 个,则推式子可得 \(g=1-\frac {1-g}{2^k}\))
D2T2 语言
- \(20pts\):暴力扫描每一条路径,复杂度 \(O(mn^2)\)
- \(+20pts\):考虑对于每个点能联系的点集,一定是一个连通子树,可以使用建立虚树的方法求解,需要使用 \(O(1)\) 的 \(lca\),复杂度 \(O(nm+n^2)\)
- \(+20pts\)(链):考虑每条路径是一段区间,而这个区间内部可以连通,转化为一个矩形,扫描线求面积并(在一个上三角中),复杂度 \(O(m\log n)\)
- 另一个暴力:类似于上头链的解法,使用树链剖分,每条路径会被划分为 \(\log n\) 段区间,相应产生 \(\log^2n\) 个矩形,扫描线复杂度 \(O(m\log^3n)\),实测 loj 可过,zjoi现场老爷机跑不动
关于 \(100pts\) 解法,是在第二档部分分上的优化:
为了不在每个点上重新枚举所有路径,可以采用将路径在树上差分,然后每个点继承儿子信息(路径 \((x,y)\):在 \(x,y\) 处增加此路径,在 \(fa(lca(x,y))\) 处减去两次此路径
关于继承儿子信息要么是启发式合并,要么是线段树合并,二者在此题中都是 \(O(n\log n)\) 的
考虑到现在要维护虚树大小,回顾虚树的构建过程,不难发现虚树大小等于 所有点深度和 减去 将点按 \(dfs\) 序排序后相邻两点 \(lca\) 的深度和,
- 这玩意用启发式合并即可
- 也可以用线段树可以轻松维护(下标为 \(dfs\) 序,维护当前节点的标记次数、虚树大小、该区间最左最右的关键点,上传节点信息时若有标记,虚树大小等于 左右虚树大小之和 减去 左区间右端与右区间左端lca的深度)
Code
\(12\)省联考
D1T1 异或粽子
类似于NOI2010超级钢琴,明显是求最大的 \(k\) 个区间,先求个异或前缀和
考虑构建一棵 \(trie\) 树,将每个点作为右端点时对应的最大左端点求出来,放进堆里(共 \(O(n)\) 个元素)
考虑每次从堆里取出一个元素,统计答案后找到这个点的编号(右端点),再将这个右端点对应的还没取的最大左端点放进堆里……共取 \(k\) 次,复杂度 \(O((n+k)\log n)\)
至于如何维护每个右端点已经取得的左端点,记录 \(n\) 棵动态开点线段树即可(结构与 \(trie\) 相同,在每次寻找时可以快速判定子树内是否有未取得的左端点)
D2T1 皮配
D2T2 春节十二响
每个集合只有最大值有用,用最大值表示这个集合
链的部分分强烈明示处理两条链的合并,不难猜测正解就是在每个节点处合并所有的儿子
合并两个儿子的信息相当于将两个数组合并成一个新的数组,也就是说给两个数组的元素之间两两匹配,\(a,b\) 匹配出的结果为 \(\max(a,b)\)
贪心地考虑,如果现在有一个有序序列 \(\{a_1,a_2,a_3\}(a_1\leq a_2\leq a_3)\),要放入一个元素 \(b(a_1,则这个元素放入 \(a_2\) 比较优(因为这样不会改变集合最大值之和,而且能为后面的合并留下 \(a_3\) 这个更有用的选项)
并且合并时要先考虑大元素的合并,以防有小元素抢占大元素
用 set 维护,启发式合并就可做到 \(O(n\log^2n)\)
或者更干脆点,两个数组合并就按照从大到小两两合并(\(A\) 数组的第 \(k\) 大与 \(B\) 数组的第 \(k\) 大合并),用个堆即可
再者,有时间可以写成长链剖分,复杂度 \(O(n\log n)\),我懒所以代码里就直接启发式合并了
Code