(二)基本算法
二分
一种比较通用二分写法,不太用考虑边界的问题。
int l = st, r = ed, ans = ed + 1;
while (l <= r)
{
int mid = (l + r) >> 1;
if (check(mid)) ans = mid, l = mid + 1;
else r = mid - 1;
}
if (ans == ed + 1) printf("No Solution!");
else printf("%d", ans);
正确性分析:
每次 \(r\) 与 \(l\) 必有一个变化,因此不会陷入死循环。
\(ans\) 始终保存的是满足条件的 \(mid\) ,所以答案永远是当前情况下的最优解。
一个小 \(trick\):二分调题时,将错误的 \(check(val)\) 单独拎出来检查是比较好的。
例 \(1.1\):[NOIP2001 提高组] 一元三次方程求解 - 洛谷
考虑三次函数的曲线形式,找到在 \(x\) 轴两侧的点,二分答案即可。
例 \(1.2\):寻找段落 - 洛谷
通过二分答案,转化为判定性问题。
欲判定当前平均值 \(mid\) 是否可能可行:即是否有 \(\sum{(a_i - mid)} >= 0(len\in [s,t])\)
考虑将 \(a_i\) 全部减去 \(mid\) ,做一遍前缀和。对于当前 \(i\) ,\(sum[i+s]\sim sum[i+t]\) 的最大值是最优的情况,于是上单调队列即可。
例 \(1.3\):[NOIP2015 提高组] 跳石头 - 洛谷
二分答案,判定可行性:在 \(m\) 次操作内是否能使每段距离都大于 \(mid\) 。
直接遍历即可,每次遇到 \(<mid\) 的距离,就对它进行操作直到它 \(>mid\) 为止。
陷入的一个误区:
二分判定可行性的时候,误以为需要找到最优方案,来判断是否能使当前理想解成立。
于是想尽办法构造了最优方案,其实完全没有必要,每一次只用贪心地去除任何能导致当前解不成立的情况即可。
评测记录 - 洛谷(仅供参考,代码构造了最优判定方案,常数较大,不够优秀)
例 \(1.4\) :进击的奶牛 - 洛谷
类似于例 \(1.3\) 。
例 \(1.5\):刺杀大使 - 洛谷
比较板,二分 \(+BFS\) 判定。
例 \(1.6\):[NOIP2011 提高组] 聪明的质监员 - 洛谷
考虑 \(y\) 的单调性:随 \(W\) 增大而单调递减。
将 \(y\) 看做函数,考虑二分的数学意义,类似于求二次方程的解,找到最接近于 \(s\) 的解即可。
考虑如何快速求解 \(y\) ,发现记录前缀和即可。
时间复杂度 \(O(N\log N)\)
例 \(1.7\):[NOIP2012 提高组] 借教室 - 洛谷
发现订单的满足二分条件,二分需要修改的订单即可。
例 \(1.8\):[SHOI2015]自动刷题机 - 洛谷
非常显然的一道二分,刷题数明显随答案增大而减小。但是我非常脑抽的把二分 \(r\) 上界设成了 \(\max{x_i}\) ,调了很久才过。
要注意的一点是,这道题更新边界的条件是 \(cnt>=k\) ,而更新答案的条件是 \(cnt==k\) 。
\(Upd: 2022.9.6\)
例 \(1.9\): [HEOI2016/TJOI2016]排序 - 洛谷
收获:当二分的单调性不显然甚至感觉上错误的时候,不妨先想想 \(check()\) 函数的写法。
毕竟二分的单调性也依托于 \(check\) 函数而存在,根据 \(check\) 函数来判定单调性是否正确。
我的理解:二分通过你设计的 \(check\) 函数(返回 \(0, 1\) )来使得 \(check(ans)\) 单调不递减。
详细一点地解法见 NOIP复习(三)线段树 的 例\(1.4\) 。
CDQ 分治
思想:处理点对统计问题时,将序列分为两半。考虑到点对 \((i,j)\) 可以分为三类:\(i,j\le mid\),\(mid+1\le i,j\) 。上两类可以递归进入两侧序列分治处理,对于第三类单独设计算法进行处理。
\(CDQ\) 分治的一个例子就是归并排序。
例 \(2.1\):[模板]三维偏序(陌上花开)- 洛谷
首先将点对去重,以 \(x\) 为关键字排序。\(CDQ\) 分治,每次考虑左区间对于右区间的贡献,将左,右区间分别以 \(y\) 为关键字排序,双指针扫描,用树状数组维护贡献即可。
例 \(2.2\):[CQOI2017]老C的任务 - 洛谷
将询问转化为二维前缀和,离线 \(CDQ\) 分治。每个询问变为 \(4\) 个点,标记权值为 \(0\),全部与输入数据一同扔进一个序列里面。\(CDQ\) 分治即可。
例 \(2.3\):[CQOI2011]动态逆序对
添加一维时间,将问题看做统计满足条件的三元组 \((pos,val,time)\) 个数即可。
构造题
例 \(3.1\):Koishi Loves Construction
TASK1:前缀和数列构成 \(\bmod n\) 意义下的一个剩余系。
首先发现 \(n\) 必须在首位。思考构造,可以有 \(n,1,n-2,3,n-4,5\dots\)
有解的判定,发现 \(n\) 为奇数的时候,无法构造满足条件的序列,\(n\) 个数的和被 \(n\) 整除。
TASK2:前缀积数列构成 \(\bmod m\) 意义下的一个完全剩余系。
发现 \(n\) 必须在末位,\(1\) 必须在首位,若 \((n-1)!\mid n\) 显然无解。
构造 \(sum_i\equiv i\pmod n\),即 \((i-1)*a[i]\equiv i\pmod n\)
证明 \(a_i\) 互不相同:证明从略,考虑威尔逊定理的证明过程易知。
猜一手结论,最大的 \(\dfrac{n}{2}\) 个数一定可以被取到。
令小于最大值的为 \(-1\), 大于最大值的为 \(1\) 。画一个折线图,发现取最大值所在点为起点的时候满足条件。