线段树进阶

发布时间 2023-09-07 08:33:07作者: include_c

普通线段树

核心在于上传标记(pushup)和下传标记(pushdown)以及懒标记的设计。

P3373 【模板】线段树 2

维护一个加法标记和乘法标记。

下传标记时,将乘法标记更新加法标记。

标记下传实现
void pushdown(int u,int l,int r){
    int mid=l+r>>1;
    tr[u<<1].val=(tr[u<<1].val*tr[u].mul+tr[u].add*(mid-l+1))%P;
    tr[u<<1|1].val=(tr[u<<1|1].val*tr[u].mul+tr[u].add*(r-mid))%P;
    tr[u<<1].mul=(tr[u<<1].mul*tr[u].mul)%P;
    tr[u<<1|1].mul=(tr[u<<1|1].mul*tr[u].mul)%P;
    tr[u<<1].add=(tr[u<<1].add*tr[u].mul+tr[u].add)%P;
    tr[u<<1|1].add=(tr[u<<1|1].add*tr[u].mul+tr[u].add)%P;
    tr[u].add=0;tr[u].mul=1;
}

Q4.4.5.1. 区间背包问题

维护懒标记 \(f(i)\)\(g(i)\) 表示该区间的背包,在容量不超过 \(i\) 时,最大的价值、物品个数。

乘法操作时,我们直接将 \(f(i)\)\(0\le i\le m\)) 全变成 \(f(i/w)\)\(g(i)\) 全变成 \(g(i/w)\)。区间覆盖操作,我们就将 \(f(i)\) 变成 \(g(i)\times v\)

下传懒标记的操作与这些相似。

注意乘法标记要每次更新后与 \(m+1\) 取较小值,因为 \(m+1\) 等价于比 \(m\) 大的所有数。

标记下传与上传实现
void pushup(int u){
	for(int i=0;i<=m;i++){
		tr[u].f[i]=0;tr[u].g[i]=0;
		for(int j=0;j<=i;j++){
			tr[u].f[i]=max(tr[u].f[i],tr[u<<1].f[j]+tr[u<<1|1].f[i-j]);
			tr[u].g[i]=max(tr[u].g[i],tr[u<<1].g[j]+tr[u<<1|1].g[i-j]);
		}
	}
}
void pushdown(int u){
	if(tr[u].lazy1>1){
		for(int i=m;i>=0;i--)
			tr[u<<1].f[i]=tr[u<<1].f[i/tr[u].lazy1],tr[u<<1|1].f[i]=tr[u<<1|1].f[i/tr[u].lazy1],
			tr[u<<1].g[i]=tr[u<<1].g[i/tr[u].lazy1],tr[u<<1|1].g[i]=tr[u<<1|1].g[i/tr[u].lazy1];
		tr[u<<1].lazy1=min(tr[u<<1].lazy1*tr[u].lazy1,(ll)m+1);
		tr[u<<1|1].lazy1=min(tr[u<<1|1].lazy1*tr[u].lazy1,(ll)m+1);
	}
	if(tr[u].lazy2){
		for(int i=0;i<=m;i++)
			tr[u<<1].f[i]=(ll)tr[u<<1].g[i]*tr[u].lazy2,tr[u<<1|1].f[i]=(ll)tr[u<<1|1].g[i]*tr[u].lazy2;
		tr[u<<1].lazy2=tr[u<<1|1].lazy2=tr[u].lazy2;
	}
	tr[u].lazy1=1;tr[u].lazy2=0;
}