2025.9.21+7

2025.9.22

Luogu P5521

贪心 + 树形 DP。你考虑当我们得到答案时,必定是从根走向一边的子树,然后剩余的全部丢掉。因此实际上我们只需要考虑这个 \(i\) 号节点子树内能否满足条件即可。注意到这个问题是可以递归下去求解的,也就是 \(i\) 的所有儿子 \(son_i\) 也需要满足条件才能放置。题目中一个重要操作是“可以在任意时刻回收任意节点上的梅花”,唯一需要考虑的是对于子节点进行放置的顺序,由于特殊操作的存在,我们记满足放置条件的节点 \(u\)\(r = \sum_{v \in son_u} w_v\),对于每个节点按照 \(r_u - w_u\) 从大到小排序,因为这样我们才能充分利用这些多出来的梅花来使得答案最优。

Rec

Luogu P1484

一个很厉害的套路:带悔贪心。对于每个位置 \(i\) 维护其前驱 \(pre_i\) 后继 \(nex_i\),优先队列维护每次的取值,注意到选取 \(i\) 这个坑,则会失去 \(pre_i + nex_i\) 的权值。因此我们在优先队列中选取并删除权值 \(w_i\) 之后很重要的一个操作就是推入 \(w_{pre_i} + w_{nex_i} - w_i\),如果我们在之后选取了这个节点就意味着我们没有选取 \(i\) 转而选择了 \(pre_i, nex_i\),这里需要双向链表进行维护,具体地,我们直接在前驱后继中间插入新的节点即可。同时注意到 \(m\) 棵树是可以不种满的。

Rec

Codeforces 856D

还是带悔贪心,这次可以熟练一点了。考虑一个裸的贪心?维护一个小根堆表示我们购入某一枚股票的价格,如果当天价格卖出有盈利(堆顶价格 \(p_{\min}\))我们就直接卖掉然后累计进入答案,同时为了提供我们在之后反悔的可能,将当天的买入价格 \(p\) 推入堆中 2 次:

  1. 卖出又买入,为未来的重新消费提供可能
  2. 反悔:如果我们今天不卖堆顶的股票,将来某一天价格变为 \(p_f\),正常来说其贡献应该为 \(p_f - p_{\min}\),但通过这种手段可以变为 \((p - p_{\min}) + (p_f - p)\)

这样就得到了一个全局最优方案。

Rec

2025.9.23

Luogu P1792

和 P1484 一样的套路,思路完全一致,但是注意到是个环,而且卡 long long,同时要种满 \(m\) 棵树。对于无解的判定只需要检查是否有 \(m \gt \frac{n}{2}\) 即可。

Rec

Luogu P2048

一个很厉害的题,考虑暴力做法是前缀和 + \(O(n^2)\) 枚举的,考虑怎么优化这个枚举过程,对于一个固定的位置 \(i\) 作为左端点,则其右端点从 \([i + l - 1, i + r - 1]\) 中选择,又因为 \(sum_i\) 是固定的,我们肯定是要在这个区间范围内找到 \(\max_{j \in [i + l - 1, i + r - 1]}(sum_j)\)。这个可以用 RMQ 来查对吧,贪心地看,我们将 \((i, l, r, t)\) 表示位置,区间范围和最优位置推入优先队列,然后按照值大小从大到小维护,每次取出堆头累计进答案。同时要注意之后还需要再推入 \((i, l, t - 1, ?), (i, t + 1, r, ?)\) 保证下一个答案仍然可以从左右两边取到。

Rec

Luogu P4597

Slope Trick 什么的真的看不懂,来贪心吧。考虑转化题意为:对于已知序列 \(A\),找到一个序列 \(B\) 使得 \(B\) 非降且 \(\sum_{i = 1}^{n} |A_i - B_i|\)\(\min\)。令当前处理的位置为 \(i\) 同时我们已经使得 \([1, i]\) 变成了一个非降序列。我们维护一个大根堆表示 \(B\) 的取值集合,对于一个新加入来的数 \(x\) 直接推进来,如果其值小于堆顶否则就将堆顶修改为 \(x\),贪心地看,保持一个较小的最大值更容易使后面的序列满足非降。一个有趣的事实是这个东西的码和上面的带悔贪心一摸一样,洛谷上有人对这个东西进行了一个 分析

Rec

Luogu P7078

贪心 + 队列优化堆。通过假设法不难得到如果最强的蛇吃了最弱的蛇之后不会变为最弱的蛇,那么它一定会吃。我们考虑什么时候它吃完变成最弱的蛇之后还是会吃?当然是之后最强的蛇不敢吃他。也就是说 \(a_{n - 1} - (a_{n} - a_{1}) \lt a_2\),后面这个东西需要递归下去处理。通过模拟可以发现递归层数为奇数时会吃,偶数不吃。这个过程朴素地考虑是上个堆来维护的,带的这只 \(\log\) 可以用蚯蚓的那个套路用两个双端队列来优化掉。具体地,维护两个双端队列 \(p, q\) 分别存储没吃过和吃过的蛇,我们一开始的结论可以推导出除了最后的特殊情况之外 \(q\) 内的蛇不可能是 \(\min\) 的,最强的蛇直接比较两个队列末端即可,最弱的直接从 \(p\) 头部取出即可。我们不断进行模拟直到 \(p\) 空或者出现上述的递归进程,此时判断一下最强的蛇会不会一次吃即可。这样子复杂度就压到 \(O(Tn)\) 的了。

Rec

SPOJ Ada and Greenflies

话说 SPOJ 也上 Cloudflare 了,我的 OI 生涯就和 LG RMJ 一样失败。

写这个题重要的关于 \(\gcd\) 的两个结论:

  1. \(\gcd\) 的取值具有单调性:丢进 \(\gcd\) 里的数字越多,其值单调不增。我们令 \(g(i, j) = \gcd \left( \sum_{i = 1}^{j} a_i \right)\),则有 \(g(i, j) = \gcd(g(i + 1, j), a_i)\),又因为 \(\gcd(x, y) \leq y\),因此 \(g(i + 1, j) \leq g(i, j)\)
  2. \(\gcd\) 的值发生变化时,至少会减半。从上面的结论中我们可以得到,\(g(i, j)\)\(g(i + 1, j)\) 的一个约数,注意这里我们定义的 \(g(i, j) \neq g(i + 1, j)\)。我们将问题转化为了一个正整数 \(x\) 最大的真约数是多少:设 \(d\)\(x\) 的一个真约数,有 \(kd = x, k \in \mathbb{Z}\),同时 \(d \lt x, k \gt 1\),又因为 \(k\) 是个整数,所以最小也是 \(2\),得到 \(d_{\max} = \frac{x}{2}\)

现在我们可以来做这个题了,题意其实就是要求一个 \(\sum_{i = 1}^{n} \sum_{j = 1}^{n} \gcd_{i \leq k \leq j} \{ a_k \}\)。枚举一个端点 \(j\),以 \(j\) 为右端点往左取的 \(\gcd\) 必定是单调的,同时最多只有 \(\log(w), w = \max_{i \leq k \leq j}{a_k}\)\(\gcd\) 的取值。那么我们每次暴力的枚举统计合并然后乘法原理算一下贡献就可以了。具体地,我们在实现上使用 std::pair 存一下各个右端点所对应的左端点和 \(\gcd\) 取值即可。

Rec

Codeforces 817D

考虑拆贡献,记 \(f_i/g_i\) 分别表示以 \(i\) 结尾的最大值/最小值之和,则答案为 \(\sum_{1 \leq i \leq n} f_i - g_i\)。记 \(h_i\) 为满足 \(a_{h_i} \gt a_i\) 的最大的 \(h_i \lt i\),则 \(f_i\) 的推导是简单的:\(f_{i} = f_{h_i} + a_i \times (i - h_i)\),这个 \(h_i\) 是可以简单地通过单调栈维护的。\(g_i\) 计算同理,改一下符号即可。

Rec

2025.9.24

Codeforces 549F

好难的题,但是学到了很多单 \(\log\) 分治的技巧,这个题也是可以做到单只 \(\log\) 的。先来改写一下题意,\(\left( \sum_{i = l}^{r} a_i - \max_{l \leq i \leq r} \right) \bmod k = 0\) 移项之后等价于 \(\sum_{i = l}^{r} a_i \bmod k = \max_{l \leq i \leq r} a_i \bmod k\)。考虑分治,这里我们处理成和上个题差不多的思路,记录最大值 \(a_m\),对于区间 \([l, r]\),要按照 \(m\) 的位置来分治处理 \([l, m], [m + 1, r]\)。对于跨 \(m\) 的区间,要求 \((sum_r - sum_{i - 1} - a_m) \bmod k = 0\),移项,\((sum_r - a_m) \bmod k = s_{l - 1} \bmod k\)。为了便于处理,记 \(p_i = sum_i \bmod k\),有了 \((p_i - (a_m \bmod k) + k) \bmod k = p_{i - 1}\)。直接枚举复杂度会退化会 \(O(n^2)\) 的,启发式合并,总是枚举短的区间,然后在长的区间里面查询,这保证了我们每次枚举之后其区间长度至少会减半。

Rec

Luogu P5502

直接套用我们之前关于 \(\gcd\) 的两个结论就可以了,枚举右端点,最多只有 \(\log\) 级别的不同 \(\gcd\) 的左端点,每次枚举这些左端点更新答案,具体地,可以使用类似 std::vector 或者 std::queue 来维护这个左端点集合。

Rec

SPOJ GSS1

线段树查区间最大子段和。

Rec

Luogu P3367

写写模板,上了按秩合并和路径压缩,感觉我的码风和写法已经基本迭代完没有大变化了 😃。

Rec

Luogu P3367

水。由于等式的关系具有传递性,我们按照 \(e_i\)\(0/1\) 排序,先处理等式,维护出并查集,然后对于所有的查询找到是否有任意一个矛盾即可,但是这个题需要离散化,可以直接上 std::mapstd::set,注意这个并查集大小一定要 \(\times 2\) 开,初始化也要 \(\times 2\) 开。

Rec

2025.9.25

初赛出分了,比估的低不少但是也够用了懒得复查。补了一个早上和中午的 whk,晚上才能打。

Luogu P1197

正难则反,一个很典的题,分裂复杂度无法保证,考虑全部炸开然后离线下来用并查集来合并。具体地,初始时我们给每个点断开然后连通块个数 \(+ 1\),扫描当前节点 \(u\) 的出边,如果两个节点在不同并查集则合并然后个数 \(- 1\)。接下来对于从后往前每一个存活的 \(u\) 先记录下剩余连通块数,然后对于孤立的 \(u\) 我们依然增加连通块个数,扫描 \(u\) 所有存活的出边 \(v\),如果合并后在一个并查集就将连通块个数 \(- 1\)

Rec

Luogu P2024

这个题的本意应该是想让我们携带权并查集,但是实际上我们可以通过维护三个并查集分别表示当前节点、敌人和猎物,每次查询然后暴力合并就可以了,实测跑得慢了。

Rec

2025.9.26

Luogu P1196

被这个题硬控了 1.5 h+。带权并查集,对于每个节点都去记录所属链的头部、当前节点到头部节点的距离、所在集合的大小,每次合并直接头接尾,然后 \(O(1)\) 的改一下 \(tag\) 就可以了,就是在查询的过程中要随时更新,查询时直接输出两点的权值之差即可。

Rec

Codeforces 722C

套路地,我们反向考虑这个问题,依旧全部断开然后从后往前来连接左右两个相邻的并查集,由于题目要求为和,每次查询子树内的权值 \(\max\) 即可。

Rec

晚上打了一场模拟。

2025.9.27

Codeforces 335F

在铁一写过原,直接丢上来了。

这个题的本质就是要最大化免费的利润。反悔贪心的做法都是很统一的,首先将馅饼排个序,从大到小,每个免费的机会都依赖于先前购买的大馅饼,把每个免费的馅饼丢进(小根)堆里,然后枚举不同价格的馅饼,你能免费获得的馅饼数为 \(c = \min(cnt_{\text{bigger}} - 2 \times siz(), cnt_i)\),必须花钱买的有 \(\min(cnt_{\text{bigger}}, cnt_i)\) 减去上面的免费的。我们是需要具体比较每种不同的馅饼的,同时由于馅饼的数量不同必定会出现不够买的情况。所以先把全部馅饼买进来,考虑当前买进剩下的馅饼数,对于这 \(cnt_i - c\),枚举这些物品来反悔贪心,很模板的过程了:

设当前堆顶为 \(k\)

  • \(k \lt a_i\):免费的机会转让给 \(a_i\),同时我们因为要免费一个 \(k\),还得购入一个,那一次的免费机会也让给 \(a_i\)
  • \(k \gt a_i\):若购买了 \(x\),有两种选择
    • \(x, 2 \times a_i\),免费一个 \(k\)
    • \(x, k\),免费 \(2 \times a_i\)

作差然后重新丢进堆里反悔就可以了,但是由于麻烦的无个数限制,当前决策还没结束,所以先存起来然后再推进去,最后综合减去堆里的所有代价就是最小的花费。

由于是古早 CF 场,rating 评的不准,应该是紫的,黑太夸张了。

Rec

Luogu P3402

__gnu_cxx::rope 黑科技,可以直接 \(O(1)\) 复制上一次的存档用来实现可持久化,其余操作基本上是根号或者 \(\log\) 级复杂度的,非常够用,这个题直接复制就可以做简单的并查集了。同时这里有一个 trick:用给节点赋随机权值来合并子树以对抗特殊构造的数据。

Rec

Luogu P6116

来都来了,再水一道。有了 C++ 黑科技之后就和写语法题一样。

Rec

QOJ 5173

集训队互测。朴素最差的移动需要 \(2 \times (r - l)\) 次,考虑优化这个过程:\(a_i = a_{i + 1}\) 省掉一次,\(a_i = a_j\) 也可以省掉一次。记 \(f_i\) 表示 \(1 \to i\) 最多可以省掉多少操作,有转移 \(f_i = \max_{j \lt i} \{ f_j + [a_i = a_j] \}\)。接下来,贪心地选取一个节点 \(p\) 满足 \(a_l = a_r\)\(l \lt r \leq p\),每次从 \(r\) 开始往 \(p\) 跳,移动的距离就是省的步数,倍增优化。

Rec

Luogu P3457

翻译稀烂。贪心策略是显然的,对于一个点,如果其联通块内有一个位置比它更低的点放置了抽水机,那这个点一定不需要放抽水机。将所有点按照高度排序,把高的点放入四周低的点所在并查集中,每次查询这个并查集内有无抽水机,没有就添加一个抽水机。

Rec

posted @ 2025-09-22 13:21  起汐  阅读(12)  评论(0)    收藏  举报