题意

Adera 是 Microsoft 应用商店中的一款解谜游戏。异象石是进入 Adera 中异时空的引导物,在 Adera 的异时空中有一张地图。这张地图上有 NN 个点,有 N1N-1 条双向边把它们连通起来。起初地图上没有任何异象石,在接下来的 MM 个时刻中,每个时刻会发生以下三种类型的事件之一:

  1. 地图的某个点上出现了异象石(已经出现的不会再次出现);
  2. 地图某个点上的异象石被摧毁(不会摧毁没有异象石的点);
  3. 向玩家询问使所有异象石所在的点连通的边集的总长度最小是多少。

请你作为玩家回答这些问题。

这张地图是一颗树的形式,我们先对这棵树 dfsdfs,求出其时间戳

仔细思考可以发现,如果我们按照时间戳从小到大排序,把出现了异象石的节点排成一圈(首尾相连),累加相邻两个节点之间的路径长度,得到的结果即所求答案的两倍(因为每条边都会经过两次,访问一次,回溯一次)。

由于题目带有增加和删除操作,我们可以通过 setset 来维护时间戳(setset 的作用是维护一个集合,把集合中的每一个元素从小到大自动排序,支持增减,查找),这样可方便求出每个点的前驱后继。两个点之间的路径长度可以通过 LCALCA 快速求出。

如果一个节点出现了异象石,就根据时间戳,在 setset 中查找到相应位置,设插入节点为 xx,前驱后继分别为 preprenextnext,那么我们就令 ansans 减去 path(pre,next)path(pre,next),加上 path(pre,x)+path(next,x)path(pre,x)+path(next,x),如果一个异象石被摧毁,类似地,减去两条边权加上新增的边权即可。对于每个询问,直接输出 ans2\frac{ans}{2} 即可。

需要注意的是,由于维护的时间戳是首尾相连的,所以在求前驱和后继的时候需要特判首尾两个特殊情况。

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<set>
#include<algorithm>
using namespace std;
long long n,m;
long long dep[101010],dis[101010];//lca的深度,和每个点到根的距离
long long fa[101010][21],v[101010],a[101010];//v代表时间戳,a代表每个时间戳的节点
long long head[201010],cnt,tot,ans;
struct node
{
	long long next;
	long long to;
	long long num;
}e[201010];
set<long long> s;
typedef set<long long>::iterator It;
It it;//set只能通过迭代器it访问
void add(long long from,long long to,long long num)
{
	e[++cnt].next=head[from];
	e[cnt].to=to;
	e[cnt].num=num;
	head[from]=cnt;
}
void dfs(long long u,long long f)
{
	fa[u][0]=f;
	v[u]=++tot;
	a[tot]=u;
	for(int i=head[u];i;i=e[i].next)
	{
		long long v=e[i].to;
		if(v==f)
			continue;
		dep[v]=dep[u]+1;
		dis[v]=dis[u]+e[i].num;
		dfs(v,u);
	}
}//dfs求出时间戳,dep,dis,fa数组
long long lca(long long x,long long y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=20;i>=0;i--)
		if(dep[fa[x][i]]>=dep[y])
			x=fa[x][i];
	if(x==y)
		return x;
	for(int i=20;i>=0;i--)
		if(fa[x][i]!=fa[y][i])
		{
			x=fa[x][i];
			y=fa[y][i];
		}
	return fa[x][0];
}//求lca
long long path(long long x,long long y)
{
	return dis[x]+dis[y]-2*dis[lca(x,y)];
}//求两点路径
It L(It it)
{
	if(it==s.begin())//如果在开头
		return --s.end();//前驱为最后
	return --it;
}//找前驱
It R(It it)
{
	if(it==--s.end())
		return s.begin();//同理
	return ++it;
}//找后继
int main()
{
	scanf("%lld",&n);
	for(int i=1;i<n;i++)
	{
		long long x,y,z;
		scanf("%lld%lld%lld",&x,&y,&z);
		add(x,y,z);
		add(y,x,z);
	}
	dep[1]=1;
	dfs(1,0);//dfs
	for(int j=1;j<=20;j++)
		for(int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];//更新fa数组
	scanf("%lld",&m);
	for(int i=1;i<=m;i++)
	{
		char c[2];
		long long x;
		scanf("\n%s",c);
		if(c[0]=='+')
		{
			scanf("%lld",&x);
			if(!s.empty())//判断是否为空
			{
				it=s.lower_bound(v[x]);//找后继
				if(it==s.end())//如果没有
					it=s.begin();//就在开头
				long long y=*L(it);//找前驱
				ans+=(path(x,a[y])+path(x,a[*it])-path(a[y],a[*it]));//更新ans
			}
			s.insert(v[x]);//插入这个点
		}
		else if(c[0]=='-')
		{
			scanf("%lld",&x);
			it=s.find(v[x]);//直接寻找这个点,因为题目满足肯定存在才删除
			int y=*L(it);//找前驱
			it=R(it);//找后继
			ans-=path(x,a[y])+path(x,a[*it])-path(a[y],a[*it]);//更新ans
			s.erase(v[x]);//删除
		}
		else
			printf("%lld\n",ans/2);//输出答案
	}
	return 0;
}