如图,按新线段 f 取值是否大于原标记 g,我们可以把当前区间分为两个子区间。其中 肯定有一个子区间被左区间或右区间完全包含,也就是说,在两条线段中,肯定有一条线段,只可能成为左区间的答案,或者只可能成为右区间的答案。我们用这条线段递归更新对应子树,用另一条线段作为懒标记更新整个区间,这就保证了递归下传的复杂度。当一条线段只可能成为左或右区间的答案时,才会被下传,所以不用担心漏掉某些线段。
具体来说,设当前区间的中点为 m,我们拿新线段 f 在中点处的值与原最优线段 g 在中点处的值作比较。
如果新线段 f 更优,则将 f 和 g 交换。那么现在考虑在中点处 f 不如 g 优的情况:
若在左端点处 f 更优,那么 f 和 g 必然在左半区间中产生了交点,f 只有在左区间才可能优于 g,递归到左儿子中进行下传;
若在右端点处 f 更优,那么 f 和 g 必然在右半区间中产生了交点,f 只有在右区间才可能优于 g,递归到右儿子中进行下传;
若在左右端点处 g 都更优,那么 f 不可能成为答案,不需要继续下传。
除了这两种情况之外,还有一种情况是 f 和 g 刚好交于中点,在程序实现时可以归入中点处 f 不如 g 优的情况,结果会往 f 更优的一个端点进行递归下传。
最后将 g 作为当前区间的懒标记。
下传标记:
实现
constexpr double eps = 1e-9;int cmp(double x, double y) { // 因为用到了浮点数,所以会有精度误差 if (x - y > eps) return 1; if (y - x > eps) return -1; return 0;}//...void upd(int root, int cl, int cr, int u) { // 对线段完全覆盖到的区间进行修改 int &v = s[root], mid = (cl + cr) >> 1; int bmid = cmp(calc(u, mid), calc(v, mid)); if (bmid == 1 || (!bmid && u < v)) // 在此题中记得判线段编号 swap(u, v); int bl = cmp(calc(u, cl), calc(v, cl)), br = cmp(calc(u, cr), calc(v, cr)); if (bl == 1 || (!bl && u < v)) upd(root << 1, cl, mid, u); if (br == 1 || (!br && u < v)) upd(root << 1 | 1, mid + 1, cr, u); // 上面两个 if 的条件最多只有一个成立,这保证了李超树的时间复杂度}
拆分线段:
实现
void update(int root, int cl, int cr, int l, int r, int u) { // 定位插入线段完全覆盖到的区间 if (l <= cl && cr <= r) { upd(root, cl, cr, u); // 完全覆盖当前区间,更新当前区间的标记 return; } int mid = (cl + cr) >> 1; if (l <= mid) update(root << 1, cl, mid, l, r, u); // 递归拆分区间 if (mid < r) update(root << 1 | 1, mid + 1, cr, l, r, u);}