2024.7.15 训练记录
P3034 USACO - Cow Photography
看到题,第一眼考虑将五次拍照的序列经过一些方法组合得到原序列,思考后发现不好做;我们考虑不变量,每次有一个元素随意移动到新的位置,对于序列元素的绝对位置其实不怎么好刻画,因为可能会产生整体偏移;但仔细一想,这一操作对序列元素的 相对位置 影响不大,因此我们考虑序列元素的相对位置关系
思路已经有了,问题来了,怎么确定要考察的两个元素不是在该次移动的元素呢?题目其实给了一个很重要的条件 —— 一个元素最多只会移动一次,因此我们从整体考虑,对于序列中的两元素 \(a_i, a_j (i<j)\),会对其相对位置造成影响的移动 最多只会出现 \(2\) 次,即将 \(a_j\) 挪到 \(a_i\) 前或将 \(a_i\) 挪到 \(a_j\) 后,题目刚好给了 \(5\) 次拍照结果,因此我们考察元素 \(a_i, a_j\),如果有 \(3\) 次拍照结果中都有 \(i<j\),那么在原序列中就有 \(i<j\)
由此知道了原序列中任意两元素的相对位置关系,现在我们希望求出原序列,正好发现 \(sort\) 的 \(cmp\) 函数可以完美切合这个问题,因此直接任取一次拍照结果 \(sort\) 一遍,排序结果就是原序列
实现细节上,我们要记录在第 \(i(1\le i \le 5)\) 次拍照中元素 \(a_i (0 \le a_i \le 1000000000)\) 的位置,值域很大,因此使用 \(map\) 记录,开 \(O2\) 即可水过;事实上由于本题中没有查询,使用 \(umap\) 会更快,实测不开 \(O2\) 最慢点约 \(700ms\),可过
时间复杂度 \(O(nlogn)\)
P4188 USACO - Lifeguards S
我们考虑先求出所有区间覆盖的 时间点,再减去单个区间中与其他区间 不重叠 时间点的数量最小值;注意这里是时间点,所以如果给定区间 \(l, r (l \le r)\),实际上覆盖的时间点有 \(r-l\) 个而非 \(r-l+1\) 个
先考虑如何求出所有区间覆盖的时间点。首先必不可少的肯定是先给区间按照左端点 \(sort\) 一遍;接下来使用类似双指针的思想,考虑新加入线段 \(line[i]\),参照下图,我们定义 \(borderr\) 表示新加入线段前 己经加入的线段能够覆盖的最大时间点,\(line[i].l\) 表示目前区间的左端时间点,\(line[i].r\) 表示目前区间的右端时间点
容易发现,如果 \(line[i].r \le borderr\),那么当前线段没有真正产生贡献,跳过即可;当 \(line[i].r > borderr\) 时,我们希望求出当前线段 左侧不与先前加入线段重叠的最大时间点,设为 \(recentl\),这样该线段的贡献即为 \(line[i].r - recentl\),如下图为两种不同情况,易得 \(recentl = max(borderr, line[i].l)\)
因此枚举每个区间统计即可,别忘了每次统计后还要更新 \(borderr\),直接令 \(borderr = line[i].r\) 即可,这样我们就解决了总贡献
接下来考虑如何求出单个区间的贡献。设当前区间为 \(line[i]\),首先仍然要特判 \(line[i].r \le borderr\),这说明有区间被完全包含,此时 \(break\) 后直接输出总贡献即可;最坏情况下,单个区间 \(line[i]\) 可能会与之前的区间产生重叠,也会与后一个区间 \(line[i+1]\) 产生重叠,我们考虑 \(line[i].l\) 与 \(borderr\)、\(line[i].r\) 与 \(line[i+1].l\) 的关系,左端点处最大无重叠时间点即为 \(max(line[i].l, borderr)\),右端点最大无重叠时间点即为 \(min(line[i].r, line[i+1].l)\),可以结合下图理解
因此我们统计处最小的单个区间贡献,最终用总贡献减去即可得到最终答案;实现时注意该贡献可能为 负数,此时可以特判为 \(0\) 并直接 \(break\);同时,和统计总贡献时相同,也别忘了更新 \(borderr\) 即 \(borderr = line[i].r\)
时间复杂度 \(O(nlogn)\)
P4181 USACO - Rental Service S
每个奶牛有两种分配方式,一种是将奶牛租出去,一种是卖出该奶牛生产的奶;首先注意到租奶牛这一方式对具体租哪一只奶牛没有限制,因此易得贪心,即 优先租产奶量小的奶牛,同时 优先租给价格最高的农场主;同理,卖奶时也 优先卖给价格最高的商店
因此,我们将商店按照价格 从大到小排序、将租奶牛的农场主按照租用价格 从大到小排序;我们注意到将所有奶牛排序后,必然有从头连续一部分选择租出去,剩下一部分选择卖出去,因此考虑 枚举断点 \(i\) ,钦定将第 \(1\) 到第 \(i\) 头奶牛的奶卖出去,第 \(i+1\) 到第 \(N\) 头奶牛租出去;所以,我们也要对奶牛按照产奶量 从大到小排序
这样,我们将总贡献分为两部分,一部分来自租出奶牛;这一部分很好维护,直接上 前缀和 即可,对排序后的 \(r[i]\) 数组,定义 \(sum[i]\) 为其前缀和数组,表示租出 \(i\) 头牛能得到的钱
对于卖奶牛奶的部分,虽然卖出奶的总和也可以用前缀和维护,但如果每次都从第 \(i\) 个商店向后循环处理是否能卖出奶会超时;由于我们知道相邻两次卖奶总和的 增量 (设当前奶牛为 \(i\),这个增量实际上就是 \(c[i]\)),因此可以 直接考虑其对答案的影响,记录下上一次卖完后所得所有的钱 \(sell\),当前卖到的商店位置 \(cnt\),直接进行处理即可;这里有个技巧,我们可以单独开一个变量记录当前商店中已有的牛奶 \(storemilk\),这样省去了直接改动商店原本信息的麻烦
具体的,设当前要卖出的牛奶 (增量) 为 \(milk=c[i]\),第 \(i\) 个的商店的容量为 \(store[i].q\) (实际剩余容量为 \(store[i].q-storemilk\)) 、价格为 \(store[i].p\),分情况讨论;
- 当商店剩余容量不足 \(milk\) 时
- 用 \(store[i].q-storemilk\) 更新 \(sell\)
- 更新当前要卖出的牛奶数量 \(milk\)
- 收回 \(storemilk\),将其设为 \(0\)
- 别忘了进行 \(cnt+1\)
- 当商店剩余容量够 \(milk\) 时
- 用 \(milk\) 更新 \(sell\) (特别注意!这里不是 \(storemilk+milk\))
- 更新 \(storemilk\) (因为这里 \(store[i].q\) 的变化实际上直接体现在了 \(storemilk\) 的变化上)
- 跳出循环即可,因为已经卖光了
最终统计出当前的总价值,加上租出的价值,与最终答案 \(ans\) 比较,这里如果 当前总价值 $ \le ans$,其实 可以直接 \(break\) ,因为容易证明价值变化分界点唯一
代码
P4265 - USACO Snow Boots S
考虑 \(DP\);设 \(dp[i]\) 表示到第 \(i\) 格所穿的靴子编号 (也就是丢弃了 \(dp[i]-1\) 双靴子),考虑转移,显然到第 \(i\) 格只能由前面的格子转移而来,因此枚举 \(j (1 \le j < i)\);因为我们要么直接穿着在第 \(j\) 格的靴子走到第 \(i\) 格,要么在第 \(j\) 格脱下一些靴子再走到第 \(i\) 格,所以再枚举 \(k (dp[j] \le k \le B)\),表示到达第 \(i\) 格时所穿的靴子
我们想从第 \(j\) 格穿着第 \(k\) 个靴子走到第 \(i\) 格,需要: - 第 \(k\) 个靴子能够走在第 \(j\) 格的雪上 - 第 \(k\) 个靴子能够走在第 \(i\) 格的雪上 - 第 \(k\) 个靴子能走的距离至少是 \(i\) 到 \(j\) 的距离
转移方程很简单,就是 \(dp[i] = min(dp[i], k)\),答案即为 \(dp[N]-1\),注意不要忘记 \(-1\)
初始情况为 \(dp[1] = 0\),因为一开始不穿靴子,另外由于需要求最小值,别忘了在最开始 \(memset\) 一下
时间复杂度 \(O(N^2B)\)
P4264 USACO - Teleportation S
不会。咕咕咕。