【题目描述】
原题来自:Contest Hunter Round #56
在 Adera 的异时空中有一张地图。这张地图上有 NN 个点,有 N−1N−1 条双向边把它们连通起来。起初地图上没有任何异象石,在接下来的 MM 个时刻中,每个时刻会发生以下三种类型的事件之一:
地图的某个点上出现了异象石(已经出现的不会再次出现);
地图某个点上的异象石被摧毁(不会摧毁没有异象石的点);
向玩家询问使所有异象石所在的点连通的边集的总长度最小是多少。
请你作为玩家回答这些问题。下图是一个例子,灰色节点表示出现了异象石,加粗的边表示被选为连通异象石的边集。
【输入】
第一行有一个整数 NN,表示点的个数;
接下来 N−1N−1 行每行三个整数 x,y,zx,y,z,表示点 xx 和 yy 之间有一条长度为 zz 的双向边;
第 N+1N+1 行有一个正整数 MM;
接下来 MM 行每行是一个事件,事件是以下三种格式之一:
+x+x:表示点 xx 上出现了异象石;
−x−x:表示点 xx 上的异象石被摧毁;
??:表示询问使当前所有异象石所在的点连通所需的边集的总长度最小是多少。
【输出】
对于每个 ?? 事件,输出一个整数表示答案。
【输入样例】
6
1 2 1
1 3 5
4 1 7
4 5 3
6 4 2
10
+ 3
+ 1
?
+ 6
?
+ 5
?
- 6
- 3
?
【输出样例】
5
14
17
10
【提示】
数据范围与提示:
对于 30% 的数据,1≤ n,m ≤ 1031≤ n,m ≤ 103 ;
对于另 20% 的数据,地图是一条链,或者一朵菊花;
对于 100% 的数据,1≤ n,m ≤ 105,1 ≤ x,y ≤ n,x≠ y,1 ≤ z ≤ 1091≤ n,m ≤ 105,1 ≤ x,y ≤ n,x≠ y,1 ≤ z ≤ 109 。
思路:这里求树上连接多个点的最短路径运用了很巧妙的做法,就是先根据各个异象石结点在树上的时间戳排序(dfs遍历顺序),然后每相邻的两个结点(包括首尾)之间求最短路,最后把这些路求和就是答案的两倍,由于这个过程相当于围绕着要求的路径走了一圈,每条路都走过并且是走两遍!然后注意开long long,并且注意不能每次直接从头遍历到尾去找那个插入或者删除的地方,这样会超时,应该用find。
#include<bits/stdc++.h>
using namespace std;
//这里求树上连接多个点的最短路径运用了很巧妙的做法,就是先根据各个异象石结点在树上的时间戳排序(dfs遍历顺序)
//然后每相邻的两个结点(包括首尾)之间求最短路,最后把这些路求和就是答案的两倍,由于这个过程相当于围绕着要求
//的路径走了一圈,每条路都走过并且是走两遍!然后注意开long long,并且注意不能每次直接从头遍历到尾去找那个插入
//或者删除的地方,这样会超时,应该用find。
typedef long long ll;
const int maxn=1e5+5;
vector<int> v[maxn];
vector<ll> w[maxn];
int fa[maxn][31]={},dep[maxn]={};
ll cost[maxn][31]={};
int n,m,dfn=0;
struct node{
int id,tm;//时间戳
bool operator<(const node &a)const
{
return tm<a.tm;
}
}nd[maxn];
set<node> st;//其实可以set<int,cmp>,cmp为自己定义的排序函数
void dfs(int root,int fno)
{
nd[root].tm=++dfn;
nd[root].id=root;
fa[root][0]=fno;
dep[root]=dep[fa[root][0]]+1;
for(int i=1;i<31;i++)
{
fa[root][i]=fa[fa[root][i-1]][i-1];
cost[root][i]=cost[fa[root][i-1]][i-1]+cost[root][i-1];
}
int sz=v[root].size();
for(int i=0;i<sz;i++)
{
if(v[root][i]==fno)continue;
cost[v[root][i]][0]=w[root][i];
dfs(v[root][i],root);
}
}
ll lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
int tmp=dep[y]-dep[x];
ll ans=0;
for(int j=0;tmp;j++,tmp>>=1)
{
if(tmp&1)ans+=cost[y][j],y=fa[y][j];
}
if(x==y)return ans;
for(int j=30;j>=0&&x!=y;j--)
{
if(fa[x][j]!=fa[y][j])
{
ans+=cost[x][j]+cost[y][j];
x=fa[x][j];
y=fa[y][j];
}
}
ans+=cost[x][0]+cost[y][0];
return ans;
}
int main()
{
ios_base::sync_with_stdio(false),cin.tie(nullptr);
cin>>n;
for(int i=1;i<n;i++)
{
int a,b;
ll c;
cin>>a>>b>>c;
v[a].push_back(b);
v[b].push_back(a);
w[a].push_back(c);
w[b].push_back(c);
}
dfs(1,0);
cin>>m;
ll ans=0;
while(m--)
{
char c;
int a;
cin>>c;
if(c=='?')cout<<ans/2<<"\n";
else
{
cin>>a;
node pre={-1,-1};
if(c=='+')
{
st.insert(nd[a]);
set<node>::iterator it=st.find(nd[a]),prit=it,nxit=it;
prit--;
nxit++;
if(it==st.begin())pre=*st.rbegin();//prit判断不了,那就用it
else pre=*prit;
if(nxit==st.end())nxit=st.begin();
//下面这三行根据上面走路径的原理去理解,就是多走了一个小分支,随便画个图就能理解
ans+=lca(pre.id,it->id);
ans+=lca(it->id,nxit->id);
ans-=lca(pre.id,nxit->id);
}
else
{
set<node>::iterator it=st.find(nd[a]),prit=it,nxit=it;
prit--;
nxit++;
if(it==st.begin())pre=*st.rbegin();
else pre=*prit;
if(nxit==st.end())nxit=st.begin();
ans-=lca(pre.id,it->id);
ans-=lca(it->id,nxit->id);
ans+=lca(pre.id,nxit->id);
st.erase(nd[a]);
}
}
}
return 0;
}