[KTSC 2025] 粒子对撞 / particles

[KTSC 2025] 粒子对撞 / particles

题意

给出一棵树,要求在线支持以下操作:

  • 在树上没有粒子的一个点放一个粒子。

  • 删除树上一个没有粒子的点和连接该点的边

  • 所有连通块中粒子两两配对最多组成多少对粒子(这里简化了原题面,易证)

思路

我们考虑对整棵树求一个欧拉序,设 \(in_x,out_x\) 表示节点 \(x\) 开始 dfs 时和结束 dfs 时的时间戳,\(v_x\) 表示时间为 \(x\) 对应节点的权值。

我们可以发现,对于一个节点 \(x\)\(\sum_{i=in_x}^{out_x} v_i\) 即为该节点和它的子树权值之和。但是我们可以发现删除一个节点时,可能会出现在该节点上面的点统计答案时把该节点对应的子树权值一起算进去的情况。

因为我们求的是欧拉序,所以每个节点 \(x\) 会有 \(v_{in_x},v_{out_x}\) 两个权值。因此我们设 \(root_x\) 为该节点对应连通块的根节点,\(v_{in_x}\) 为该节点是否存在粒子,存在为 \(1\),不存在为 \(0\)\(v_{out_{root_x}}\) 为该节点对应连通块粒子个数的相反数。每次统计答案时计算 \(\sum_{i=in_{root}}^{out_{root}-1} v_i\) 即可得到该连通块的节点个数。

考虑这样做有什么好处。可以发现对于一个连通块,不在它的子树中的连通块不会被统计到,在它子树内的连通块虽然被统计到了,但是子树内的连通块的粒子个数都会被 \(v_{out_{root}}\) 抵消。

现在来考虑如何维护 \(v_i\)\(root_i\)。可以发现 \(v_i\) 只会有单点修改,区间求和的操作,所以我们可以使用树状数组维护。

虽然对于节点 \(x\)\(root_x\) 是不好维护的,因为每次删除一个节点会使得一段不连续的区间根节点发生变化,但是对于 \(root_x\) 的时间戳是很好维护的。我们维护一个新数组 \(irt_x\) 表示时间戳为 \(x\) 的节点所对应根节点的时间戳(入点出点不重要),求对应根节点可以通过映射数组求出。每次删除一个节点 \(x\) 时对于所有新形成的连通块根节点 \(v\),对于 \(irt_{in_v}, irt_{in_v+1},\cdots,irt_{out_v}\)\(in_v\)\(\max\) 即可。

因为对于在它子树内的连通块 \(y\)\(irt_y\) 是一定大于 \(in_v\) 的,所以不会被修改,对于该连通块 \(z\) 的值 \(irt_z\) 是一定小于 \(in_v\) 的,所以会被修改。可以发现 \(irt_x\) 只会有区间最值修改,单点求值的操作,可以使用线段树维护,时间复杂度 \(O(n\log n)\)

代码

#include <bits/stdc++.h>
#define ls (u<<1)
#define rs ((u<<1)|1)
#define mid ((l+r)>>1)
using namespace std;
int n,in[400005],out[400005],to[400005],f[400005],cnt,nw;
int c[1600005],lz[1600005],cc[400005];
vector<int> t[400005];
void add(int x,int v){
	while(x<=2*n) cc[x]+=v,x+=(x&-x);
}
int _qu(int x){
	int res=0;
	while(x) res+=cc[x],x-=(x&-x);
	return res;
}
int qu(int x,int y){
	return _qu(y)-_qu(x-1);
}
void push_up(int u){
	c[u]=max(c[ls],c[rs]);
}
void push_down(int u){
	if(lz[u]) lz[ls]=max(lz[ls],lz[u]),lz[rs]=max(lz[rs],lz[u]),c[ls]=max(c[ls],lz[u]),c[rs]=max(c[rs],lz[u]),lz[u]=0;
}
void build(int u,int l,int r){
	if(l==r){
		c[u]=1;
		return;
	} 
	build(ls,l,mid);
	build(rs,mid+1,r);
	push_up(u);
}
void upd(int u,int l,int r,int x,int y,int v){
	if(x<=l&&r<=y){
		c[u]=max(c[u],v);
		lz[u]=max(lz[u],v);
		return;
	}
	push_down(u);
	if(x<=mid)
		upd(ls,l,mid,x,y,v);
	if(mid<y)
		upd(rs,mid+1,r,x,y,v);
	push_up(u);
}
int qur(int u,int l,int r,int x){
	if(l==r)
		return c[u];
	push_down(u);
	if(x<=mid)
		return qur(ls,l,mid,x);
	else
		return qur(rs,mid+1,r,x);
}
void dfs(int x,int fa){
	in[x]=++cnt,to[cnt]=x,f[x]=fa;
	for(int v:t[x])
		if(v!=fa) dfs(v,x);
	out[x]=++cnt,to[cnt]=x;
}
void initialize(int N,vector<int>A,vector<int>B){
	n=N;
	build(1,1,2*n);
	for(int i=0;i<N-1;i++){
		t[A[i]+1].push_back(B[i]+1);
		t[B[i]+1].push_back(A[i]+1);
	}
	dfs(1,0);
}
int generate(int v,bool result){
	v++;
	int rt=to[qur(1,1,2*n,in[v])];
	if(result){
		nw-=qu(in[rt],out[rt]-1)/2;
		add(in[v],1),add(out[rt],-1);
		nw+=qu(in[rt],out[rt]-1)/2;
	}
	else{
		nw-=qu(in[rt],out[rt]-1)/2;
		for(int u:t[v]){
			if(u!=f[v]){
				add(out[rt],qu(in[u],out[u]-1));
				add(out[u],-qu(in[u],out[u]-1));
				upd(1,1,2*n,in[u],out[u],in[u]);
				nw+=qu(in[u],out[u]-1)/2;
			}
		}
		nw+=qu(in[rt],out[rt]-1)/2;
	}
	return nw;
}
posted @ 2025-03-26 11:08  WuMin4  阅读(85)  评论(0)    收藏  举报