[SDOI2016]游戏(树剖+李超树)

李超树的模板题。

考虑题目给出的 \(a \times dis + b\),整体不好处理,可以\(s\)\(t\) 的路径从 \(p \gets lca(s,t)\) 处分开。左边是 \(-a \times dis_i + (a \times dis_s + b)\),右边是 \(a \times dis_i + (a \times (dis_s - 2 \times dis_p) + b)\),都是以 \(dis_i\) 为自变量的一次函数,可以用李超树维护。

更大的问题在于查询:树上的路径最小值在序列上就是区间最小。这意味着,我们要在李超树上像普通线段树一样维护一个区间最小值。然而,因为李超树主要维护线段,无法进行标记的下传和上推,我们必须引入一个技巧——标记永久化

访问到一个区间,如果查询区间完全包含它,直接取出。如果不完全包含,就要用本区间的信息实时更新答案。在此题中,就呈现为 p->minx=min(p->minx,min(p->ls->minx,p->rs->minx)); 的形式。

李超树和树剖老生常谈,不再赘述细节。

下面是 AC 代码核心部分:

//计算点值
inline LL calc(int id,int x){return k[id]*dis[f[x]]+b[id];}
//线段树主体(略去build()和外部调用函数)
struct segtree{
	int l,r;LL id,minx;segtree *ls,*rs;
	segtree(){ls=rs=0,minx=INF,id=1;}
	#define mid (((p->l)+(p->r))>>1)
}*rt;
inline void refresh(segtree *p){
	p->minx=min(p->minx,min(p->ls->minx,p->rs->minx));
}
void _change(segtree *p,int l,int r,int id){
	if(l<=p->l&&p->r<=r){
		if(calc(id,p->l)<=calc(p->id,p->l)&&calc(id,p->r)<=calc(p->id,p->r)){
			p->id=id;
			p->minx=min(p->minx,min(calc(id,p->l),calc(id,p->r)));//标记永久化*1
			return;
		}
		//略去按照斜率和中点讨论的部分
		p->minx=min(p->minx,min(calc(id,p->l),calc(id,p->r)));//标记永久化*2
		refresh(p);return;
	}
	if(l<=mid) _change(p->ls,l,r,id);
	if(r>mid) _change(p->rs,l,r,id);
	refresh(p);
}
LL _query(segtree *p,int l,int r){
	if(l<=p->l&&p->r<=r) return p->minx;
	LL ans=INF;
	if(b[p->id]!=INF) 
		ans=min(calc(p->id,max(l,p->l)),calc(p->id,min(r,p->r)));//标记永久化*3
	if(l<=mid) ans=min(ans,_query(p->ls,l,r));
	if(r>mid) ans=min(ans,_query(p->rs,l,r));
	return ans;
}
//main()函数调用部分
k[++cnt]=0,b[cnt]=INF,build(rt=new segtree,1,n);
while(m--){
	int op=rd(),s=rd(),t=rd(),p=lca(s,t);
	if(op==1){
		int x=rd(),y=rd();
		k[++cnt]=-x,b[cnt]=y+x*dis[s];change(s,p,cnt);
		k[++cnt]=x,b[cnt]=y+x*(dis[s]-dis[p]*2);change(t,p,cnt);
	}
	else printf("%lld\n",query(s,t));
}

THE END

posted @ 2021-09-02 22:56  q0000000  阅读(31)  评论(0编辑  收藏  举报