HDU4578
这是一道超好的题,麻烦归麻烦,加深了我对lazytag的认知,做了它我才知道原来之前在lazytag下传顺序这里我并没理解完全透彻顺序是怎么决定的。
lazytag下传的优先级本质上是可以任意的,但我们不该给自己制造难度,该选择最方便于我们计算的办法。其中核心思想是能不能用当前下传顺序比较容易且在不损失精度、尽量避免繁琐的情况下表示出其它下传顺序,下面我们就这道题来讲一下先乘后加的优先级是怎么分析出来的:
首先,只要有把一个区间变成一个数c的操作,不管这个结点此前打上多少别的lazytag,都是要作废的,即这一步会覆盖之前所有lazytag,故优先级最高。至于为什么应该不难理解,就是覆盖了。
而我们我们要思考的问题是有lazytag时,改查操作需要下传lazytag改变值时是先乘后加还是先加后乘呢?
①先乘后加:
我们定义lazytag下传顺序为先乘后加,如果一个元素所在区间有两个lazytag分别为add(加)、mul(乘)待下传至这个元素上,顺序如果是先乘后加则按照这个默认顺序下传改变元素值即可;但如果是先加后乘的改变呢?我们怎么将先加后乘的式子(2)用先乘后加类型的式子(1)等价表示呢?
a×mul + add(1)
(a + add)×mul = a×mul + add×mul(2)
由上,(2)与(1)唯一不同的是在更新a时,将add标记上乘mul,二者即等价,因此将(2)转化为(1)这样先乘后加的类型只需在添加乘法标记mul时先将加法标记add乘上mul即可将先加后乘转化为先乘后加的形式。
②再看先加后乘:
(a + add)×mul(1)
a×mul + add = a×mul + add×mul/mul = (a + add/mul)×mul(2)
由上可见,如将(2)转化为(1)的形式则会出现add/mul,若此时mul不能整除add,则会造成精度损失,且如果存在取mod问题,则add/mul会存在求mul的逆元,极大影响了效率。
由上,改查操作时的lazytag下传顺序决定为先乘后加,那按这种顺序下传后对于下传到的区间的原有lazytag的修改是怎么实现的?
设:当前节点编号 o , addv[o]是当前节点的加法标记,mulv[o]是当前节点的乘法标记 sumv[o]是当前节点的求和值, ls表示左儿子的编号,rs表示右儿子的编号。(注:这里对当前结点的lazytag的定义为当前节点的 sumv值已维护好了,但是儿子的sumv和lazytag都还没维护好。)
以左儿子举例,根据先乘后加的顺序,我们先维护ls的sumv,即给儿子乘上自己的乘法标记,再加上自己的加法标记即可。
sumv[ls] = sumv[ls]×mulv[o] + addv[o]×(R-L+1)
此时ls的 sumv 值就维护好了,但如果ls还有lazytag呢?它的lazytag又该怎么维护?
已知:lazytag的下传顺序是先乘后加, 如果儿子已经有lazytag,它的lazytag如果要维护一个值 s, 它维护后的值 s’应该是:s′ = s×mulv[ls] + addv[ls]。
那现在又给他加上了 o 的lazytag, 那么维护后的值 s’‘应该是:
s′′=s′×mulv[o] + addv[o]
= (s×mulv[ls] + addv[ls])×mulv[o] + addv[o]
= (s×mulv[ls]×mulv[o]) + (addv[ls]×mulv[o] + addv[o])(3)
设:mulv[ls]’ 、addv[ls] '为维护后的lazytag,能推出:
s’’=s×mulv[ls]′ + addv[ls]′(4)
又由(3)与(4)上式等价,我们即可得出儿子lazytag的维护方式:即在维护乘法标记时,直接在儿子s的乘法标记mul[s],维护加法标记时,先在儿子s的加法标记add[s]上乘一下自己的乘法标记mul[o],然后再加上自己的加法标记add[o]即可完成。
mulv[ls]′=mulv[ls]×mulv[o]
addv[ls]′=addv[ls]×mulv[o]+addv[o]
至此,不论之前键入的修改顺序【此处是离线的】是对lazytag进行先乘后加还是先加后乘,我们人为规定一律都按先乘后加规则下的下传lazytag和求sumv的顺序求解,因此对于每个结点儿子s的sumv和lazytag,在每一步都被这种认为规定的固定规则等价维护,任务完成。
【以上分析部分源自luogu题解】
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10,mod=10007;
struct node
{
int l,r;
int sum1,sum2,sum3;
int add,mul,lazy;
}tr[N*4];
void pushup(node& p, node& l, node& r)
{
p.sum1 = (l.sum1 + r.sum1) % mod;
p.sum2 = (l.sum2 + r.sum2) % mod;
p.sum3 = (l.sum3 + r.sum3) % mod;
}
void pushup(int u)
{
pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}
void build(int u,int l,int r)
{
tr[u]={l,r,0,0,0,0,1,0};
if(l==r) return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void pushdown(node& op, int add, int mul, int lazy)
{ //需要分类pushdown
const int len = op.r - op.l + 1;
if (lazy) {
lazy %= mod;
op.sum1 = lazy * len % mod;
op.sum2 = lazy * lazy % mod * len % mod;
op.sum3 = lazy * lazy % mod * lazy % mod * len % mod;
op.add = 0, op.mul = 1; op.lazy = lazy;
}
if (mul != 1) { //对于乘法和加法的标签处理, 需要遵循先乘后加的原则
op.sum1 = op.sum1 * mul % mod;
op.sum2 = op.sum2 * mul % mod * mul % mod;
op.sum3 = op.sum3 * mul % mod * mul % mod * mul % mod;
op.add = mul * op.add % mod;
op.mul = op.mul * mul % mod;
}
if (add) {
op.sum3 = (op.sum3 + len * add % mod * add % mod * add % mod + 3 * op.sum2 % mod * add % mod + 3 * op.sum1 % mod * add % mod * add % mod) % mod;
op.sum2 = (op.sum2 + 2 * add % mod * op.sum1 % mod + len * add % mod * add % mod) % mod;
op.sum1 = (op.sum1 + len * add % mod) % mod;
op.add = (op.add + add) % mod;
}
}
void pushdown(int u)
{
node &p=tr[u];
pushdown(tr[u<<1],p.add,p.mul,p.lazy);
pushdown(tr[u<<1|1],p.add,p.mul,p.lazy);
p.add=0,p.mul=1,p.lazy=0;
}
void modify(int u,int pos,int l,int r,int c)
{
if(l<=tr[u].l&&r>=tr[u].r)
{
if(pos==1) pushdown(tr[u],c,1,0);
else if(pos==2) pushdown(tr[u],0,c,0);
else pushdown(tr[u],0,1,c);
return;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) modify(u<<1,pos,l,r,c);
if(r>mid) modify(u<<1|1,pos,l,r,c);
pushup(u);
}
int query(int u,int l,int r,int pos)
{
if(l<=tr[u].l&&r>=tr[u].r)
{
if(pos==1) return tr[u].sum1;
else if(pos==2) return tr[u].sum2;
else return tr[u].sum3;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
int res=0;
if(l<=mid) res+=query(u<<1,l,r,pos);
if(r>mid) res+=query(u<<1|1,l,r,pos);
return res%mod;
}
int main()
{
int n,m;
while(scanf("%d %d", &n, &m), n | m)
{
build(1,1,n);
for(int i=1;i<=m;i++)
{
int op,l,r,c;
scanf("%d%d%d%d",&op,&l,&r,&c);
if(op==4) printf("%d\n",query(1,l,r,c));
else modify(1,op,l,r,c);
}
}
return 0;
}
本文详细分析了树状数组(区间查询与修改)中懒惰标记的处理策略,特别是先乘后加的顺序。讨论了为何选择先乘后加作为优先级,并通过实例解释如何将先加后乘转化为先乘后加,同时展示了如何在下传过程中维护节点的sumv和lazytag。最后,给出了相应的C++代码实现。
2343

被折叠的 条评论
为什么被折叠?



