账可以晚还,合同不能抄错
西岸有座海关。海关最忙的时辰,往往没有船。
船还在雾里,命令已经到了。国王喜欢临时改规矩,今天允许带三把伞,明天允许带五把,后天又给所有伞加一根柄。
抄写员若逐张改通行证,天亮前只能改完自己的名字。
老关长想了个偷懒办法。他把新命令封进不同颜色的信封,挂在各个柜台后面。
通行证先照旧放着,客人来办事,才把经过的信封一只只拆开。
办法运行得很好。直到某天来了两道命令。
第一道命令让所有伞变成原来的五倍长。第二道命令给每把伞再加三寸。
学徒把信封摞在一起,封口朝外,谁也看不出先后。
一位船长拿出旧伞试验。先拆蓝信封,后拆红信封,伞长刚好合规。
顺序倒过来,伞长多出十二寸。同一把伞,足够戳穿海关的屋顶。
屋顶漏水的时候,东岸海关送来一套新表格。
两边的栏目看起来差不多。东岸把“船数、命令数、校准常数”写在一行,西岸把最后一项另起一页。
学徒照抄旧流程,第一行就把命令数读成了海风。
老关长把两张表压在砚台下面。潮水退了一寸,又涨回来。
他这才发现,柜台真正保管着两种东西:已经兑现的结果,以及尚未拆封的变化。
前者随时要能回答,后者可以晚点落地。至于一张表来自哪座海关,属于另一份约定。
多年以后,海关把这种做法编进教材。名字很难听,像一棵专门收留迟到命令的树。
简化题面
┌─ [P2023] [AHOI2009] 维护序列
├─ 平台: 洛谷
├─ 题意: 维护数列,支持区间乘、区间加、区间求和,结果均对 P 取模。
├─ 输入: 第一行 n、P;第二行初始序列;随后给出 m 次操作。
├─ 输出: 每次区间求和操作的结果。
├─ 数据范围: n、m <= 1e5。
├─ 迁移题: P3373 首行改为 n、q、m,操作含义相同。
└─ 完整题面: https://www.luogu.com.cn/problem/P2023;https://www.luogu.com.cn/problem/P3373
代码里,命令也可以晚一点落地
线段树节点保存一段序列的和。节点上的 lazy 保存已经算进当前和、还没交给子节点的变换。
父节点的和必须随时可回答。子节点可以暂时保留旧结果。
查询或部分修改碰到分界线,down 把延期变换交给两个孩子;修改回到上层,up 用孩子的结果重算父节点。
void apply(int p, int l, int r, long long mul, long long add) {
tree[p] = (tree[p] * mul + add * (r - l + 1)) % MOD;
tagMul[p] = tagMul[p] * mul % MOD;
tagAdd[p] = (tagAdd[p] * mul + add) % MOD;
}
最后一行不能凭手感写。旧变换记作 g(x)=mc*x+ac,新变换记作 f(x)=mp*x+ap,先后顺序展开为:
f(g(x)) = (mc*mp)x + (ac*mp+ap)
新乘法会碰到旧加法。单位变换是 (1,0)。
把乘法标记填成 0,整段数据都会被当成黑玻璃。
查询下钻会改变标记的存放位置,逻辑上的序列值保持不变。
父节点已经把延期变化算进总和,孩子只是收到同一份变化;查询返回后无需额外 up。部分修改真正改变了孩子,才需要重算父节点。
小模数让错误提前靠岸
大模数很宽容。少取一次模,错误可以在海上漂很久。
把模数压到 21。初值故意放大。
每次操作同时喂给线段树和一份朴素数组,build、up、query 的取模边界会很快露出来。
当树上状态始终满足“已经取模的区间和”这条合同,输出只负责读数。对拍程序负责把违规行为拖到岸上。
灯塔重新开门时,阿九把北海的纸和东海的纸分开存放。镜片仍旧一片一片地亮,塔门上的规章继续晚一点执行。
海水退去。有人来问那一小段光线的亮度。
阿九先看树上的那张纸,再去看镜片。
发布信息草案
- 标题:
账可以晚还,合同不能抄错 - 摘要:双懒标记的关键是把延期操作看成函数,并在模板迁移时同时核对输入合同与状态合同。
- 标签:线段树、懒标记、仿射变换、模板迁移、对拍

