NFLSOJ #12461 -「NOIP2021模拟赛1001杭二」树套树(线段树合并+兔队线段树)

题面传送门

首先考虑一条链的情况。BST 这个东西不好维护,思考如何转化为我们熟悉的数据结构。注意到一棵 BST 等价于一棵按照权值排序之后,以时间 \(t\) 为键值建立的笛卡尔树,其中每个点的键值大于其父亲(根节点除外),而看到笛卡尔树我们可以很自然地想到单调栈,因此我们考虑将操作离线下来扫描线,我们用线段树维护一个以键值为下标,时间为值的线段树,也就是说我们对时间轴、下标、值做一个置换。这样每次操作可以写成,往序列中某个位置插入一个数 / 删除一个数 / 查询序列建出来的 Cartesian tree 中,某个点祖先的权值之和。根据笛卡尔树那套理论,一个点在笛卡尔树上所有祖先,等于从这个点向左向右分别跳,每次跳到离这个点最近的,且值小于这个点的值的位置,经过的所有点的集合,也就是说,对于一次查询,我们假设时间为 \(t\),查询的值为 \(v\),那么我们就要从 \(v\) 开始,不断向左跳,每次找到在它左边离它最近,且值小于 \(t\) 的位置,然后将 \(t\) 赋为这样的位置上的值。对于右边也重复一遍同样的操作。

考虑如何维护之,上面操作等价于动态地对于每个点维护在它前面离其最近的、且值小于该点的值的位置 \(pre_i\),以及它后面的离其最近的、且值小于该点的值的位置 \(nxt_i\),这个直接维护起来不容易,因此考虑直接在询问时向后跳 \(pre/nxt\) 来计算答案,以 \(nxt\) 为例,我们考虑将待查询区间(也就是一段后缀)拆成若干个小区间,然后对于每个小区间在线段树上表示的节点,我们从这样的节点开始 DFS,每次我们考察其左子树的最小值,如果左子树最小值 \(<\) 当前的 \(t\),那么先进左子树后进右子树,否则直接进右子树,如果递归到了叶子节点那么就考察叶子节点的值与当前 \(t\) 的大小关系,如果 \(t>\) 叶子节点的值就令答案加上当前节点的值,正确性显然,时间复杂度也显然是错的——可以被卡掉平方(

考虑优化,注意到如果我们递归了左子树,那么从左子树出来时,\(t\) 肯定等于左节点的最小值,因此我们考虑设一个 \(sum_k\)\(sum_{rs_k}\) 表示当递归到 \(k\) 时,\(t=\) 左子树最小值时递归右子树得到的和,其中 \(rs_k\) 表示 \(k\) 的右儿子。这样 pushup 时需要在右子树内调用一遍 query 函数,这有点类似于 P4198 楼房重建——二者需要在 pushup 时调用其他函数。这样 query 时,如果需要进入左子树,就直接进入左子树递归,右子树的贡献直接加上 \(sum_{rs_k}\)。粉兔神仙为此写过一篇论文《从<楼房重建>出发浅谈一类使用线段树维护前缀最大值的算法》 专门介绍了这一类线段树,所以这类线段树又被称为“兔队线段树”(wjz:感觉在迫害小粉兔)。这样,由于在修改时,pushup 中还要一个 \(\log\) 的常数,总复杂度就是 \(n\log^2n\)。对于 \(pre\) 的情况——直接把序列翻转一下就和 \(suf\) 的情况一样了,可以写一个结构体 struct solver 解决两种情况。

最后思考如何扩展到树上,其实如果你理解了链的情况,那么树的情况就很 trivial 了。发现这个模型很像 P4556 Vani有约会 雨天的尾巴 /【模板】线段树合并,按照那题的套路树上差分一下,然后一遍 DFS 推标记即可。复杂度还是 \(n\log^2n\)

const int MAXN=2e5;
const int MAXP=MAXN<<5;
const int LOG_N=18;
const int INF=0x3f3f3f3f;
int n,qu,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;u32 key[MAXN+5];
struct qry{int opt,x,y;u32 k;} q[MAXN+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int fa[MAXN+5][LOG_N+2],dep[MAXN+5];
void dfs0(int x,int f){
	fa[x][0]=f;
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;
		dep[y]=dep[x]+1;dfs0(y,x);
	}
}
int getlca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=LOG_N;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
	if(x==y) return x;
	for(int i=LOG_N;~i;i--) if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
struct segtree{
	int a[MAXN+5],tim[MAXN+5],rt[MAXN+5],ncnt=0;
	struct node{int ch[2],sum,mn,cnt;} s[MAXP+5];
	int calc(int k,int l,int r,int t){
		if(!k) return 0;if(l==r) return (s[k].mn<t)?a[l]:0;
		int mid=l+r>>1;
		if(!s[k].ch[0]||s[s[k].ch[0]].mn>=t) return calc(s[k].ch[1],mid+1,r,t);
		return calc(s[k].ch[0],l,mid,t)+s[s[k].ch[1]].sum;
	}
	void pushup(int k,int l,int r){
		int mid=l+r>>1;
		s[k].mn=min((s[k].ch[0])?s[s[k].ch[0]].mn:INF,(s[k].ch[1])?s[s[k].ch[1]].mn:INF);
		if(s[k].ch[0]) s[s[k].ch[1]].sum=calc(s[k].ch[1],mid+1,r,(s[k].ch[0])?s[s[k].ch[0]].mn:INF);
	}
	int merge(int x,int y,int l,int r){
		if(!x||!y) return x+y;int z=++ncnt,mid=l+r>>1;
		if(l==r) return s[z].cnt=s[x].cnt+s[y].cnt,s[z].mn=min(s[x].mn,s[y].mn),z;
		s[z].ch[0]=merge(s[x].ch[0],s[y].ch[0],l,mid);
		s[z].ch[1]=merge(s[x].ch[1],s[y].ch[1],mid+1,r);
		return pushup(z,l,r),z;
	}
	void modify(int &k,int l,int r,int p,int v){
		if(!k) k=++ncnt;
		if(l==r) return s[k].cnt+=v,s[k].mn=(s[k].cnt)?tim[l]:INF,void();
		int mid=l+r>>1;
		if(p<=mid) modify(s[k].ch[0],l,mid,p,v);
		else modify(s[k].ch[1],mid+1,r,p,v);
		pushup(k,l,r);
	}
	int curmn=INF;
	int query(int k,int l,int r,int ql,int qr){
		if(!k) return 0;
		if(ql<=l&&r<=qr){
			if(s[k].mn>=curmn) return 0;
			int res=calc(k,l,r,curmn);
			curmn=s[k].mn;//printf("%d\n",curmn);
			return res;
		} int mid=l+r>>1;
		if(qr<=mid) return query(s[k].ch[0],l,mid,ql,qr);
		else if(ql>mid) return query(s[k].ch[1],mid+1,r,ql,qr);
		else return query(s[k].ch[0],l,mid,ql,mid)+query(s[k].ch[1],mid+1,r,mid+1,qr);
	}
	int query(int u,int v,int l,int r){
		curmn=v;return query(rt[u],1,qu,l,r);
	}
} L,R;
vector<int> add[MAXN+5],del[MAXN+5],qry[MAXN+5];
u32 res[MAXN+5];
void modify(int u,int x,int v){
//	printf("modify %d %d %d\n",u,x,v);
	L.modify(L.rt[u],1,qu,qu-x+1,v);
	R.modify(R.rt[u],1,qu,x,v);
}
int query(int u,int p,int t){
//	printf("query %d %d %d\n",u,p,t);
	return L.query(u,t,qu-p+1,qu)+R.query(u,t,p,qu);
}
void dfs(int x,int f){
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;dfs(y,x);
		L.rt[x]=L.merge(L.rt[x],L.rt[y],1,qu);
		R.rt[x]=R.merge(R.rt[x],R.rt[y],1,qu);
	}
	for(int id:add[x]) modify(x,q[id].k,1);
	for(int id:del[x]) modify(x,q[id].k,-1);
	for(int id:qry[x]) res[id]=query(x,q[id].k,id);
}
int main(){
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	scanf("%d%d",&n,&qu);
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	dfs0(1,0);for(int i=1;i<=LOG_N;i++) for(int j=1;j<=n;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
	for(int i=1;i<=qu;i++){
		scanf("%d",&q[i].opt);
		if(q[i].opt==1) scanf("%d%d%u",&q[i].x,&q[i].y,&q[i].k);
		else scanf("%d%u",&q[i].x,&q[i].k);
		key[i]=q[i].k;
	} sort(key+1,key+qu+1);
	for(int i=1;i<=qu;i++) q[i].k=lower_bound(key+1,key+qu+1,q[i].k)-key;
	for(int i=1;i<=qu;i++){
		L.a[qu-q[i].k+1]=key[q[i].k];L.tim[qu-q[i].k+1]=i;
		R.a[q[i].k]=key[q[i].k];R.tim[q[i].k]=i;
		if(q[i].opt==1){
			int lc=getlca(q[i].x,q[i].y);
			add[q[i].x].pb(i);add[q[i].y].pb(i);
			del[lc].pb(i);del[fa[lc][0]].pb(i);
//			printf("%d\n",lc);
		} else qry[q[i].x].pb(i);
	} dfs(1,0);
//	for(int i=1;i<=qu;i++) printf("%d %d %d %d\n",L.a[i],R.a[i],L.tim[i],R.tim[i]);
	for(int i=1;i<=qu;i++) if(q[i].opt==2) printf("%u\n",res[i]);
	return 0;
}
posted @ 2021-10-16 19:05  tzc_wk  阅读(120)  评论(1)    收藏  举报