正难则反
概述
以前学习计数 时,总是见到 “正难则反” 的思想,这种思想在计数题中的确十分常用:如果合法的情况不好求,就求出全部情况以及不合法的情况,一减就能得出答案;求 恰好、至少 则需要用容斥,具体可以在这篇博客内查看。
但是,正难则反 的应用可不止局限于此,事实上,非常广泛,我认为:可以将表面的东西形容为 “正”,容易忽视的东西形容为 “反”,正难则反 则是对于 “反” 操作,这样说还是太抽象了,我们结合一些实际的例子进行理解。
DP 状态设计&转移优化
动态规划的状态设计,毫不夸张的讲,这是 最重要的一步,万事开头难。为啥会用到这种思想,我们结合例题进行分析。
这类 题目通常有如下特征:同一个数只能用 次,要维护巨大的集合但是时间不允许也做不到排除等效冗余。转移优化一般要贴近最终的状态,比如题目要求删除,那么最终状态是保留一些,我们处理保留的情况的值。还有一种经典的优化手段,在下面的下面的 会讲到。
T1
不难注意到,题目问合法序列的方案数,也就是我们要填一个合法序列,如果依次考虑每一位填什么,容易发现不好转移,因为问题中存在进位,即:如果对于当前计算出的结果,随机的填上某个 ,根本计算不出会增加多少个 ,那么转移是失败的。
正难则反,如果考虑每个位置上填的数字不行,我们能不能考虑将每个数字填入位置,也就是反过来。
于是我们重新考虑进位的影响,容易发现,如果从小到大考虑每种数填的次数,那么进位是可以算出来的。可以参照下图:
不难发现,两个 的进位是 ,可以直接传递到 的计算中,因为两者仅仅相差一位,相当小学的竖式计算,所以我们从小到大依次考虑每种数填多少次,可以解决进位的问题。那我们就可以设状态了。
设 表示现在填到第 位,考虑完了 ,目前前一位的进位(如上图两个 的进位)是 ,已经计算出 个 的方案数。
接着,枚举 位填 个,于是,新的进位是 ,这一位的数字是 ,所以有如下转移:
边界条件:
在转移的时候,还需要乘上权值,以及考虑放置位置,即盒子放球(可以出现空)的问题,这是显然的,不再赘述。
T2
给定一棵 个节点的树,顶点编号为 ,你需要给每个顶点设置一个标号 满足:
- ( 树上相邻)
求方案数。
按照题目的说辞,要给每个节点设置一个标号,如果我们直接考虑每个节点填什么,有两点比较麻烦:
- 每个标号不能重复使用。如果不用 等记录,怎么解决。
- 相邻的节点标号差不能超过 。如果记录每个点的父亲和自己填的什么,考虑子树进行转移,那么同样存在重复使用标号的问题。
无数困难暗示我们,此路不通。
正难则反,考虑每个标号填在那个节点。于是设出这样的状态: 表示现在将 的标号都填好了,标号 填在 节点上,标号 填在 节点上,现在填好的位置集合是 。
这样就可以转移了。具体的转移以及复杂度分析:戳这里
T3
假如我们依次考虑每个分部的管理情况,其实不太好弄,毕竟计算贡献的时候,需要知道每个分部管理村落的交,必须要将所有村落的管理情况进行记录,但这在时间或空间上都是不允许的,于是我们转换思路,将 “多个分部同时管理一个村落” 改成 “一个村落同时被多个分部管理”。
设 表示村落 被集合 管理所获得的最大贡献,是一个树形背包的形式,考虑这样转移:
最后加上 被 管理的贡献即可,可以用高维前缀和求得。
T4
现在有 个区间 ,每个区间有个权值 。我们把这 个区间当成 个点,如果两个区间有交(包括端点),就在这两个区间之间连边,形成了一个区间图。
现在希望删除一些区间,使得每个连通块大小不超过 。输出删除区间最小的权值和。
这是一道关于转移优化的题目。
题目让删除一些区间,求删除的最小的权值和;显然可以反过来想,求保留的最大权值和。这样考虑问题会有好处(后面讲)。
我们考虑经过删除,所有区间长什么样:是一堆连续段,中间断开。我们发现,断开的位置恰好对问题进行了划分,所以考虑枚举断开的位置进行转移即可。转移方程式详见这里。
至于好处,可以避免在边界处区间是否需要删除的讨论。
其他
T1
具体题目不记得了,大概是安排 的值,求下面式子的最值。
一道 .
显然,如果对于每个区间直接计算最小值乘上区间长度,就变成枚举 的取值,直接算贡献,显然会超时。我们反过来想,对于位置 的 ,如果钦定他是最大值,那么可以影响多少个区间呢。
在区间 内,位置 除了 和 不能影响,其他都能影响,所以我们可以直接计算出贡献,然后对于区间 和 分别计算。这不就是区间 吗。。。
设 表示区间最大值不能超过 的最大值的最值(因为钦定了一个最大值,所以区间内的其他值都必须小于等于这个值),于是可以这样转移:
这样在原题可以拿到 ,优化需要凸包,没学。
但是空间上可能存在部分问题,如果不要 这一维关于最大值得限制,想想我们 出来的值是否正确。
一种直观的想法是:如果不限制最大值,那么不能满足题目要求,计算出来的结果自然是错误的。我们举个例子,比如:对于中间的数 ,我们取了,获得了 的贡献,不考虑最大值的限制,我们取了左边区间的 ,获得了 的贡献,此时 显然成立。
对于这一段相同的区间,假设我们先取 ,并获得 的贡献,再取 ,获得 的贡献,这是合法的操作,并且 是显然成立的,也就是说,虽然在转移过程中虽然出现了不合法的决策,但是这一定不会成为最优解,也就不会影响答案的选取。
所以最终 方程优化成:
空间复杂度骤降。。。
T2
给定一个由数字
1
到9
组成的字符串 ,每次询问一个 位的由数字1
到9
或?
组成的模板串,求 中有多少个子串能匹配上该模板串。?
可以匹配上任意数字。例如,
1?2?3
可以匹配上12233
,19293
,但是不能匹配12345
。保证询问的模板串中至多有 个问号。
最暴力的想法肯定是先将 的所有长度为 的子串存下来,然后暴力填模板串的问号,直接进行匹配,由于最多有 个问号,所以时间复杂度是 ,可以通过 较小的数据。
有没有优化的方式呢,直接照着这个思路想貌似不太行,我们能否不处理模板串,转而预处理原串 呢(正难则反)?
显然可以,我们可以在对 的每个子串任意填上 个问号,储存起来,在查询的时候直接查找即可,时间复杂度是 .
显然,两个都过不了
我们能否将 前面系数平衡一下?
我们考虑在 的每个子串任意填上 个问号,然后在模板串中暴力填上 个括号,进行匹配,这样时间复杂度就降为 ,使用手写哈希表储存,注意常数,可以通过此题。
T3
这是一类考虑权值不行,转而将权值进行排序,考虑下标的套路,但是题目不记得了,留坑代填。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效