例 1. \(\text{CF1408E Avoid Rainbow Cycles}\)

\(a_i+b_j\) 的限制很不好满足,我们可以考虑重构图。对于 \(j\in A_i\) 的关系,在 \(j,A_i\) 之间连边权为 \(a_i+b_j\) 的边。我们发现,在新图上的环就等价于原图的 "彩虹环"。所以跑一个最大生成树即可。


例 2. \(\text{P6623 }\)

一些闲话:看到二进制想拆位,怎么现在还记不住 /kk.

不妨单独考虑第 \(j\) 位的、节点 \(x\) 对祖先 \(y\) 的贡献。显然,如果 \(v_x+\text{dis}(x,y)\) 的第 \(j\) 位有值,就一定满足(\(k\ge 0\)

\[(2k+1)\cdot 2^j\le v_x+d_x-d_y<(2k+2)\cdot 2^{j} \]

还是惯用套路,我们将 \(v_x+d_x-d_y\) 拆成两部分便于统计,那么 \(y\) 获得 \(x\) 的贡献 \(2^j\) 必须满足

\[v_x+d_x-(2k+2)\cdot 2^j<d_y\le v_x+d_x-(2k+1)\cdot 2^j \]

可以发现,两侧的限制在模 \(2^j\) 意义下都相等,所以实际可以利用差分 + 桶就可以完成限制。具体地,遍历到 \(x\) 时将 \((v_x+d_x)\bmod 2^j\) 的桶异或上 \(2^j\),这样正好就抵消了。

另外还有一个 \(\rm trie\) 树做法。每次加一操作,只需要沿着右儿子走,回溯时候递归交换左右儿子去模拟进位。然后再加上线段树合并。因为总共有 \(n\) 个数被插入,删除次数就是 \(\mathcal O(n)\) 的,总删除时间为 \(\mathcal O(n\log v)\).


例 3. \(\text{[HNOI 2014] }\)道路堵塞

一些闲话:虽然这题复杂度看上去不太对,但是动态 \(\rm spfa\) 的思想确实可以借鉴。(你什么时候借鉴过

首先描述算法流程:先从 \(1\) 号点开始跑一个 \(\rm spfa\),不走给定最短路边,且走到最短路上的点也不从此点拓展;同时,当拓展到最短路上的点 \(v\) 时,我们在小根堆中插入 \(v\),它的权值是从 \(1\) 号点到 \(v\) 的最短路 \(d_v\) 加上 \(v\)\(n\) 的最短路(这个可以直接用给定最短路边逆序求得)。完成后,查询小根堆中是否存在 \(v>1\) 的点(即能 保证 没有使用第 \(1\) 条给定边),输出最小权值。接着,我们用第 \(1\) 条给定边更新此边对应的点 \(\rm to\)\(d\) 值,从 \(\rm to\) 开始再跑 \(\rm spfa\)……之后的操作都是类似的。

重要的是 延迟更新 的思想:对于 \(\rm ban\) 掉第一条给定边的情况,显然给定最短路上的点都不需要拓展,直接取用给定最短路。小根堆的正确性也很显然,因为点 \(n\) 被拓展时一定会被插入,而且 \(i\) 是递增的。

什么,你问我复杂度?复杂度是什么,能吃吗 /yiw


例 4. \(\text{[NOI 2017] }\)整数

题目中 \(|a|\le 10^9,b,k\le 30n\) 其实给出了一些暗示:二进制分块。为了方便,我们将 \(32\) 位压为一块,压成一个 unsigned int。维护加法和减法两个结构,那怎么处理询问呢?事实上,我们只用考虑成加减两个结构在第 \(k\) 位是否相同,再判断小于第 \(k\) 位的部分,加结构减去减结构时需不需要借位。后面的部分实际就是查找小于第 \(k\) 位的第一个加减结构 不同 的位,这个可以用 \(\rm set\) 维护,具体来说,在对某块进行修改时,就将修改后的 unsigned int 和另一结构同编号块的 unsigned int 进行比较。模拟进位是均摊 \(\mathcal O(1)\) 的,因为你可以将每一块看作二进制下的一位,而 \(|a|\le 10^9\) 实际上就保证了每次初始对一块不能加超过 \(1\),就可以拓展二进制计数器的复杂度分析了。

整体复杂度大概是 \(\mathcal O(n\log n)\),还有一些实现上的细节:对于块的进位,unsigned int 可以直接加,再判断修改前后的大小关系就知道是否进位;另外不能直接 >>32,需要分成 >>31>>1 两步计算。


例 5. \(\text{[CTSC 2008] }\)网络管理

恶补我的数据结构

带修树链第 \(k\) 大问题。对于树上问题,第一个思路就是用树剖转化成序列问题。这样一个朴素的想法就呼之欲出了:修改时用树状数组套权值线段树单点修改;查询就加个树剖,用树状数组前缀和相减得到树剖区间。时间复杂度是 \(\mathcal O(n\log^3 n)\) 的。事实上,这类 可加性 问题(统计权值区间的数字个数)有一个通用的降低复杂度的方法:假设查询链 \((u,v)\),只需 \((1,u)+(1,v)-(1,\text{lca})-(1,f_{\text{lca}})\) 即可。所以只需在单点 \(u\) 上维护 \((1,u)\),就可以去掉树剖的 \(\log\),这就是 "区间修改(子树),单点查询" 的问题,可以用 \(\rm dfn\) 序解决,用树状数组维护 \(\rm dfn\) 序即可。


例 6. [省选联考 2021 A/B 卷] 卡牌游戏

题目要求算极差,一个经典的想法是将所有数字排序,答案就是某个区间的左右端点之差。需要注意的是,区间内部可以有不选的数字,而区间外的数字必定是不选的,所以我们 \(\rm check\) 的时候应该只关注区间外的情况。好抽象啊


例 7. [省选联考 2021 A/B 卷] 滚榜

一些闲话:时隔一年再来看这道题,还是觉得不会做 qwq,完了完了我真的感觉自己省选要凉了……

看数据范围想到状压,朴素 \(\mathtt{dp}\) 就是选择集合 \(S\) 中的队伍成为第一名的时间,上一支成为第一名的队伍是 \(i\),上一支队伍的 \(b_i\),总共用了多少题目数,这是显然过不了的。

这题一个非常重要的点其实是题目要求的是 "最终排行榜上队伍的排名情况" 而不是 "每支队伍的 \(b_i\) 取值情况"。所以事实上我们只需要确定每支队伍成为第一名的时间(这是一个排列)即可,我们甚至不用限制 \(\sum b_i=m\),只需保证 \(\sum b_i\le m\),这样最后一个成为第一名的队伍直接补到 \(m\) 即可。

另外题目还有一条很重要的线索,\(b_i\) 不降,这也就意味着假设 \(j\)\(i\) 之后成为第一名,\(b_j=b_i+\delta(\delta\ge 0)\). 而这个 \(\delta\)\(\{b\}\) 没有半毛钱的关系!想到什么?差分,也即费用提前计算。预处理 \(\delta_{i,j}\)(注意这里处理的是最少花费),从 \(i\) 转移到 \(j\) 时就加上 \(\delta_{i,j}\cdot \text{coe}\),其中 \(\rm coe\) 是在 \(i\) 之后成为第一名的队伍数。这样就去掉 "上一支队伍的 \(b_i\)" 这一维,根据上文所述,由于这是最小花费,所以 \(\sum b_i\) 可以取到 \([0,m]\).

复杂度 \(\mathcal O(n^2m\cdot 2^n)\),但是跑得飞快。

另外再提一嘴,\(b_i\) 不降还可以考虑 \(\text{meet-in-middle}\).


例 8. \(\rm Intervals\)

题目大意:给定 \(m\) 条规则形如 \((l_i,r_i,a_i)\),对于一个 01 串,其分数的定义是:对于第 \(i\) 条规则,若该串在 \([l_i,r_i]\) 中至少有一个 1,则该串的分数增加 \(a_i\).

你需要求出长度为 \(n\)01 串中的最大分数。

说句闲话:卧槽随便选一道题自己就不会,真的被整焦虑了。

遇到类似 "在区间 \([l,r]\) 中存在",我们可以考虑在端点计算贡献,对于此题不妨选点 \(r\). 确定了一个端点,显然我们只需要知道离 \(r\) 最近的 \(1\) 就知道是否包含 \(1\) 了。所以设 \(dp_{i,j}\) 为选到第 \(i\) 个位置,在之前最近的 \(1\) 的位置为 \(j\) 的最大分数。那么用线段树维护,新建 \(dp_{i,i}=\min dp_{i-1,j}\),然后再更新 \(dp_{i,j}\leftarrow \sum_{r_k=i,l_k\le j}a_k\).


例 9. \(\text{[UER #8] }\)打雪仗

一些闲话:第一次做通信题,好耶!

考虑到 \(\rm Bob\) 啥也不发是非常浪费的,我们可以考虑通过 \(\rm Bob\) 来提醒一下 \(\rm Alice\) 哪些需要发。先考虑 \(n=1000,m=1600\) 的部分分,它在提示 \(m=\frac{3}{2}n\) 的做法:\(\rm Alice\) 每发一个数,\(\rm Bob\) 就提醒她下一个数需不需要发送,最坏情况是连着 \(n\) 个数字不需要发送,\(\rm Alice,Bob\) 均会浪费 \(n/2\) 次发送机会,再加上剩下的 \(n\) 次发送,总共是 \(\frac{3}{2}n\) 次发送。

对于 \(n=1000,m=1350\) 的部分分,这也在提示 \(m=\frac{4}{3}n\) 的做法:将序列划分成三段,至少有一段有 \(n/3\) 个数被选择,我们直接让 \(\rm Alice\) 输出这一段的所有数(段的编号可以被两位二进制数表示);对于剩下的两段,由 \(\rm Bob\) 先发送是否需要这个位置,\(\rm Alice\) 再进行输出,对于 \(\rm Alice\) 这是 \(\frac{2}{3}n\) 级别的。

均摊思想真的好妙啊。


例 10. [NOI Online #3 提高组] 优秀子序列

说个笑话:这篇题解咕了两年。

首先看到 \(\varphi(1+\sum_{i=1}^m a_{b_i})\) 就开始疑惑:这玩意看上去好大啊,算的了吗?事实上,如果将每个数的二进制表示看作集合,那么一个优秀子序列的交集一定为空!这也就意味着把它们相加不会产生进位,所以数字和在 \(2^{18}\) 这个范围内。考虑令 \(dp_i\) 为二进制集合为 \(i\) 的优秀子序列个数,答案就是 \(\sum dp_i\cdot \varphi(i+1)\)\(\mathtt{dp}\) 转移就是枚举子集:\(dp_s\leftarrow dp_t\cdot c_{s\setminus t}\)(为了避免转移重复,可以强制 \(s,t\)\(\rm lowbit\) 相同),其中 \(c_i\) 是数字 \(i\) 的出现次数(注意这里没有考虑零,这是较特殊的一种情况),因为子序列是没有顺序的。最后答案再乘上 \(2^{c_0}\) 即可,因为零对 \(\varphi\) 没有贡献,只对方案数有贡献。复杂度 \(\mathcal O(3^{18})\).


例 11. \(\text{CF830C Bamboo Partition}\)

一些闲话:非常矛盾的心态 —— 明明很紧张却又不想学了……要打起精神来啊!

取模先化简成减法

\[\sum_{i=1}^n d-\left(a_i-\left\lfloor\frac{a_i-1}{d}\right\rfloor\cdot d\right)\le k\\ \sum_{i=1}^n \left\lfloor\frac{a_i-1}{d}\right\rfloor \le \frac{k+\sum a_i}{d}-n \]

左边就是整除分块的形式,有趣的是,左边的求和只有 \(n\sqrt V\) 种取值。单独来看显然是 \(\sqrt V\) 种取值,由于每次 \(d\) 的移动至少带来一个 \(a\) 的变化,所以总共有 \(n\sqrt V\) 种取值。枚举每一种取值,然后计算 符合条件 最大的 \(d\),具体而言,对于取值相同的 \(d\),肯定值越小越容易满足条件,先判断最小值能否满足条件,接着将 \((k+\sum a_i)/(s+n)\) 与答案取 \(\rm max\).


例 12. [九省联考 2018] 秘密袭击 coat

一些闲话:感觉这个枚举的思路和之前做过的两道题挺像的。

枚举城市 \(i\) 为选取城市集合中第 \(k\) 危险的城市(注意还需要在危险度相等时设立偏序关系),那么将更危险的城市标记为 \(1\),反之为 \(0\)。接下来就需要计算包含 \(i\) 的连通块中有 \(k-1\)\(1\) 的连通块个数,再乘上 \(d_i\) 累加进答案。令 \(dp_{i,j}\) 为以 \(i\) 为根的子树(必选 \(i\))的有 \(j\)\(1\) 的连通块的个数,转移略。复杂度 \(\mathcal O(n^2k)\).


posted on 2021-07-22 21:07  Oxide  阅读(23)  评论(0编辑  收藏  举报