暑假集训题目合集-1
考虑朴素的 \(O(nWm)\) 转移: \[dp_{j}=\max_{k=0}^{m_i}(dp_{j-k\times w_i}+k\times v_i)\]
尝试改写成适合单调队列优化的形式,按余数分组:
设当前 \(r=j\bmod w_i\),\(t=\lfloor j/w_i\rfloor\),状态转移方程改写为: \[dp_j=\max_{k=0}^{\min(m_i,t)}(dp_{r+(t-k)\cdot w_i}+k\cdot v_i)\] 令 \(t'=t-k\),则 \(k=t-t'\): \[\begin{aligned} dp_j&=\max(dp_{r+t'\cdot w_i}+(t-t')\cdot v_i)\\ &=\max(dp_{r+t'\cdot w_i}+t'\cdot v_i)+t\cdot v_i \end{aligned}\] \(t'\in[\max(0,t-m_i),t]\),构成一个滑动窗口。
时间复杂度 \(O(nW)\)。
code
1 |
|
[NOI2005] 瑰丽华尔兹
单调队列优化 DP。容易想到 \(O(nmT)\) 的方程:设 \(dp_{t,i,j}\) 表示在 \((i,j)\) 位置经过 \(t\) 时间的答案,容易写出转移: \[dp_{t,i,j}=\max(dp_{t-1,i,j},dp_{t-1,i',j'}+1)\] 考虑优化成 \(O(nmk)\),则我们首先将状态优化为 \(dp_{k,i,j}\) 表示第 \(k\) 个区间,在 \((i,j)\) 位置的答案。转移变为 \[dp_{k,i,j}=\max(dp_{k-1,i',j'}+dis)\] 具体地,假设当前在向 \(i\) 的正方向滑行,我们可以写出: \[dp_{k,i,j}=\max_{pos=i-len_k}^i(dp_{k-1,pos,j}-pos)+i\] 其余三个方向同理,这就可以单调队列优化了。
可以滚动数组去掉 \(k\) 这一维。
code
1 |
|
闲话:《海上钢琴师》真的非常好看! ### [SCOI2010] 股票交易 设 \(dp_{i,j}\) 表示 \(i\) 天结束时手上有 \(j\) 个股票时的最大收益。转移: - 什么也不做: \[dp_{i,j}=dp_{i-1,j}\] - 买入: \[dp_{i,j}=\max(dp_{i-W-1,j-k}-k\times AP_i)\] - 卖出: \[dp_{i,j}=\max(dp_{i-W-1,j+k}+k\times BP_i)\]
第一个式子没有研究价值,我们看后两个。将式子转化,用 \(k'\) 分别代替 \(j-k\) 和 \(j+k\): \[dp_{i,j}=\max(dp_{i-W-1,k'}+k'\times AP_i)-j\times AP_i\quad j-k'\le AS_i\] \[dp_{i,j}=\max(dp_{i-W-1,k'}+k'\times BP_i)-j\times BP_i\quad k'-j\le BS_i\] 显然可以单调队列。code
1 |
|
玉蟾宫
插一个单调栈典题。我们考虑枚举行数,对于一行内,维护每一列向上延伸最多的
F
数量(类似直方图)。例如,对于样例1 | R F F F F F |
F
数量应当是: 1 | F F F |
F
数量维护单调栈。时间复杂度 \(O(nm)\)。
code
1 |
|
并查集
[APIO2008] 免费道路
题意:一个边权为 \(0\) 或 \(1\) 的图,求边权和为 \(n-k\) 的生成树。 首先 Kruskal 求最大生成树,求得必须要加入的 \(0\) 边。 再 Kruskal 一遍,先将 \(0\) 边加至 \(k\) 条,然后一直加 \(1\) 边直到求出生成树。code
1 |
|
[AHOI2013] 连通图
线段树分治+可撤销并查集。 在时间轴上开线段树,每个节点维护std::vector
表示当前区间需要加的边,使用可撤销并查集在回溯时撤销当前节点添加的边。
code
1 |
|
Day2
Trie
[JSOI2009] 电子字典
Trie 题怎么能用 Trie 做呢?
由于串长极小,直接字符串哈希,实现时需要注意以下几个点: -
使用自然溢出哈希; - 记得去重,去重一定不要用 std::set
,会
T 飞。
code
1 |
|
[USACO12DEC] First! G
建 Trie,在 Trie 上查找时对字母连边,拓扑排序判环。code
1 |
|
[IOI 2008] Type Printer
显然先建出 Trie,考虑怎样的遍历顺序更优。发现这不是老熟人吗?
code
1 |
|
笛卡尔树
[TJOI2011] 树的序
考虑 BST 每个节点记录权值 \(k\) 和插入的时间 \(t\),则 \(k\) 满足 BST 的性质,\(t\) 满足小根堆的性质,这样构建的 BST 是一棵 Treap。题意转化为:重新分配 \(t\),使得生成序列最小。
既然 \(t\) 维度是小根堆,那么满足父亲小于后代。贪心地想,较小的 \(t\) 分配顺序为:父亲 \(>\) 左子树 \(>\) 右子树,也就是 BST 的前序遍历。 笛卡尔树同样满足 Treap 的性质。它的 \(k\) 与 \(t\) 正好与本题的 BST 相反。则我们只需交换 \(k\) 和 \(t\) 并 \(O(n)\) 构建笛卡尔树即可。
code
1 |
|
数据结构
最大异或和
可持久化 01 trie 模板题。操作与主席树类似,对于查询,设我们已经维护了所有的前缀异或和 \(sum_i=\oplus_{j=1}^ia_j\),则只需最大化 \(sum_{p-1}\oplus sum_n\oplus x\)。后两项是给定的,则我们只需贪心求出答案即可。
code
1 |
|
[十二省联考 2019] 异或粽子
类似[NOI2010] 超级钢琴的贪心,由于 \(a_i\oplus a_j=a_j\oplus a_i\),只需使用堆查询最大的 \(2k\) 个,再令答案除以 \(2\)。可以用 Trie 查询全局第 \(k\) 大,方法类似线段树上二分。code
1 |
|
Day3
字符串哈希
[POI 2006] PAL-Palindromes
容易发现,两个回文串 \(A\),\(B\) 组合成新回文串当且仅当 \(AB\) 等于 \(BA\)。设 \(A\) 的哈希值为 \(a\),\(B\) 的哈希值为 \(b\),有 \[a\times base^{|B|}+b=b\times base^{|A|}+a\] 简单移项 \[\frac{a}{base^{|A|}-1}=\frac{b}{base^{|B|}-1}\] 开哈希桶维护即可。code
1 |
|
[POI 2012] OKR-A Horrible Poem
\(A\) 的一个子串 \(B\) 能成为 \(A\) 的一个完整周期,当且仅当其长度为 \(A\) 长度的因数。直接枚举因数。判断是否是完整周期的方法:设 \(h_{l,r}\) 表示 \([l,r]\) 的哈希值,则当一个长为 \(len\) 的子串满足 \(h_{l,r-len}=h_{l+len,r}\) 时,其为一个完整周期。先跑一遍线性筛降低枚举因数的时间复杂度。code
1 |
|
[ONTAK2015] Tasowanie
双指针+二分+哈希。code
1 |
|
KMP
[HNOI2008] GT考试
设 \(f_{i,j}\) 表示长串匹配前 \(i\) 个字符,短串匹配前 \(j\) 个字符的方案数。答案为: \[ans=\sum_{i=0}^{m-1} f_{n,i}\] 设 \(g_{i,j}\) 表示匹配到第 \(i\) 位时加入一个数字后匹配到第 \(j\) 位的方案数,可以用 KMP 求出。 \[f_{i,j}=\sum_{k=0}^{m-1}f_{i-1,k}\times g_{k,j}\] 注意到这是矩阵乘法的形式,矩阵快速幂 \(O(m^3\log n)\)。在转化成矩阵的过程中,\(f_{i,j}\) 被转化为一个一行 \(m-1\) 列的矩阵。
code
1 |
|
Manacher
感觉自己没理解 Manacher 啊,改天重学一次。 ### [国家集训队] 拉拉队排练 由于只需要找长度为奇数的回文串,无需在字符间插入#
,直接跑 Manacher,开差分数组 \(sum\),表示当前长度的回文串数量。当位置
\(i\) 的最长回文半径为 \(p_i\) 时,容易想到半径长度小于等于 \(p_i\) 的都是回文串,则我们只需在差分数组
\(sum_1\) 处加 \(1\),\(sum_{p_i\times 2}\) 处减 \(1\) 就行了。最后统计答案时要用快速幂。
code
1 |
|
[国家集训队] 最长双回文串
在跑 Manacher 同时维护以每个点开头和结尾的最长回文串长度。仅 Manacher 求解的答案不完全,需要再递推地扫一遍,最后枚举中点即可。code
1 |
|
Day4
搜索
搜索题真是无聊死了,就少放几道吧…… ### [CSP-S 2022] 假期计划 \(n\) 遍 BFS 求全源最短路,之后考虑枚举。将 \(5\) 个点记作 \(1,2,3,4,5\),则枚举 \(3,4\) 点,判断 \(2,5\) 是否与已选的点重复。可以预处理出每个能同时到达 \(1\) 点和另一个点的前三大点,因为它最多只有可能与 \(3,4\) 中的一个和 \(2,5\) 中的一个重合。总体时间复杂度 \(O(nm+n^2)\)。code
1 |
|
[AHOI2012] 铁盘整理
IDA* 板题。先离散化,估价函数为相邻差不为 \(1\) 的个数。code
1 |
|
Prime Gift
容易发现,\(p_i\) 越小,方案数越多。\(n=16\) 时不可接受,考虑 Meet in the middle。最坏情况下前 \(8\) 个质数 \(2,3,5,7,11,13,17,19\),在 \(10^{18}\) 内能组成 \(7039193\) 种数字。实际实现时可以将数的分配得更加均匀以降低复杂度。之后二分答案+双指针求解即可。code
1 |
|
[USACO09NOV] Lights G
Meet in the middle,开一个std::map
记录状态对应的最小操作次数,按照补集相加。
code
1 |
|
机关
考虑 A*。容易想到 \(4\) 进制状压,则状态 \(x\) 的估价 \(h(x)\) 应为当前已经旋转的次数与所有不为 \(1\) 的旋钮旋到 \(1\) 所需总步数除以 \(2\) 的和。因为最好情况是旋一个按钮时另一个正好也到了 \(1\)。每个状态记录一个 \(pre\) 表示是从哪个状态转移过来的以输出方案。
code
1 |
|
Day5
最小生成树
[NOIP 2013 提高组] 货车运输
最优情况一定是走最大生成树上的边,所以先 Kruskal 求最大生成树,接下来求树上两点间路径权值最小值,可以边权下放点权上树剖+线段树或倍增。另外,图不保证连通,实际上要对森林做操作。code
1 |
|
[BJWC2010] 严格次小生成树
跟上一道题一个套路。考虑枚举替换边的过程,首先加一条边形成环,再从环上删去最大的一条小于加入边边权的边。线段树维护区间最大值和区间严格次大值即可。code
1 |
|
Peaks
Kruskal 重构树的在线做法还是太吃操作了,我们直接离线。容易想到将边权和询问的 \(x\) 升序排序后动态加边,查询第 \(k\) 大直接线段树上二分,加边用并查集+线段树合并维护。code
1 |
|
最短路
没做。 ## 拓扑排序 ### [ZJOI2007] 最大半连通子图 容易发现,半连通子图就是若干相连的强连通分量,则找最大半连通子图转化为缩点后在 DAG 上找最长链+计数。注意去重。code
1 |
|
Day6
二分图
Machine Schedule
连接每个 \(a_i\) 和 \(b_i\),问题变为了二分图上找最小点覆盖。直接跑二分图最大匹配即可。Kőnig 定理:二分图中,最小点覆盖中的顶点数量等于最大匹配中的边数量。
code
1 |
|
[USACO05JAN] Muddy Fields G
贪心地考虑,极长地放木板一定优。将所有横着的和竖着的木板编号,相交的连边,之后二分图最大匹配。code
1 |
|
Guardian of Decency
最大独立集与最小点覆盖之和为顶点数目,这个推论适用于一般图。code
1 |
|
欧拉路径
没做。 ## 连通分量 没做。
参考资料:
https://oi-wiki.org/graph/graph-matching/bigraph-match/
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 headless-piston's Blog!
评论