前言:
学了这么长时间终于可以写一些自己想写的东西了(。・ω・)ノ゙
题目大意:
在给定的一些线段中,最多能选出多少条互不相交的线段
这是一道贪心基础题,相信大家都做过,我就不再具体阐述了
背景:
长久以来我一直以为活动分配问题应该用DP+树状数组O(nlogn)来解,但是经过xxq的提醒发现哦原来还有个O(n)的贪心做法(不算排序的时间,因为排序过程本身与决策无关),而且在内网里这道例题我本来是rk1的……然后发现自己真是沙茶,学了这么久反而用高端方法去做简单题,那我这不白学了吗TAT
但是……明明我这个DP推得没有什么问题,凭什么就比贪心慢呢?
百思不得其解,于是我就试图从《算法导论》中寻找答案,这篇文章先不谈子集系统拟阵最大权森林,然而书中思路来源全靠开脑洞,证明过程倒不是很复杂,但是“贪心算法在每一步选择当前看来最好的选择”说起来倒是简单,可谁也不能保证自己上考场的时候还能灵光一现地说哎我感觉这个DP好像可以搞成贪心,所以我们需要一种比较稳的寻找正确贪心策略的方法
经过了几天的思考我发现普通做法的时间复杂度之所以多一个log是因为记录了不必要的时域信息(当然,多记录一些信息也有好处,但是在本文中不详细讨论),而我们最终要求的结果只和频域的值有关,所以我们可以把时域和频域反过来维护(事实上贪心策略也是这么做的,只不过比较隐晦),这样我们就可以形式化地证明贪心策略的正确性了
下面请看我化腐朽DP为神奇贪心☆
很容易想到的一种DP方程:f[e[i].r]=max{f[j]}+1 (j<e[i].l)
经过上述分析我们把时域和频域反过来维护写出另一种DP方程:g[i+1]=e[j].r (g[i]<e[j].l),注意这里g同时有多个取值
然后由于问题要求尽可能地使g[i]<e[j].l成立,我们就只选取g最小的那个值,所以改写成g[i+1]=min{e[j].r} (g[i]<e[j].l)
形式看起来和一开始的那个方程还挺对称的
很明显这个方程本身就是一个贪心策略,g[i+1]从且仅从g[i]这个状态以一种方式转移而来
换句通俗的话具体地讲这个转移过程就是,每次新加进来一个活动的时候总是要选择结束时间最早的那个活动(真是一个熟悉的结论)
猜想:
可以出一道神题让右面的条件式是一个二次函数什么的,这样大家就不能靠感觉做题了(逃)