异象石【set、lca、dfn与遍历思想】

异象石

题目描述

在Adera的异时空中有一张地图。这张地图上有N个点,有N−1条双向边把它们连通起来。起初地图上没有任何异象石,在接下来的M个时刻中,每个时刻会发生以下三种类型的事件之一:
地图的某个点上出现了异象石(已经出现的不会再次出现);
地图某个点上的异象石被摧毁(不会摧毁没有异象石的点);
向玩家询问使所有异象石所在的点连通的边集的总长度最小是多少。
请你作为玩家回答这些问题。
下图是一个例子,* 节点表示出现了异象石(图中有5个),双线的边表示被选为连通异象石的边集(图中有6条)。

0
//1\\
//1\\
//1\\
*0*
/\\//\\
0*0*
//
*

输入格式

第一行有一个整数N,表示点的个数;
接下来N−1行每行三个整数x,y,z,表示点x和y之间有一条长度为z的双向边;
第N+1行有一个正整数M;
接下来M行每行是一个事件,事件是以下三种格式之一:
+x:表示点x上出现了异象石;
−x:表示点x上的异象石被摧毁;
?:表示询问使当前所有异象石所在的点连通所需的边集的总长度最小是多少。

输出格式

对于每个?事件,输出一个整数表示答案。

样例 #1

样例输入 #1

6 
1 2 1
1 3 5
4 1 7
4 5 3
6 4 2
10
+ 3
+ 1
?
+ 6
?
+ 5
?
- 6
- 3
?

样例输出 #1

5
14
17
10

提示

【数据范围】
对于30%的数据,1≤ n,m ≤ 10^3;
对于另20%的数据,地图是一条链,或者一朵菊花;
对于100%的数据,1≤ n,m ≤ 10^5,1 ≤ x,y ≤ n,x!= y,1 ≤ z ≤ 10^9。

【题目来源】Contest Hunter Round #56

核心思路

注意到,dfs一颗树,严格按照dfn序进行。

如果有 k 个点要去访问,最后回到起点

最短路径必然按照dfn序进行,由此,
ans = (dis(1,2)+dis(2,3)+dis(n-1,n)+dis(n,1))/2;

考虑不断去更新加入的贡献,找节点前后继即可。

特判处理:类似于考虑首尾相接。

AC 代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1000000+10;
map<pair<int,int>,int> mp;
vector<int> G[N] ;
int dep[N],fa[N],hson[N],sz[N],top[N],dfn[N],res[N];
void dfs1(int u,int p){//u当前访问到的,p父节点。
	fa[u] = p;
	sz[u] = 1;
	dep[u] = dep[p]+mp[{u,p}];
	hson[u] = -1;
	for(int i = 0;i < G[u].size();i++){
		int v = G[u][i];
		if(v == p)continue;
		dfs1(v,u);
		sz[u]+=sz[v];
		if(hson[u] == -1|| sz[v] > sz[hson[u]]){
			hson[u] = v;
		}
	} 
 
	
}
int cnt;//记录是第几次访问 
void dfs2(int u,int p){
    if(u == 1)top[1] = 1;
    //先遍历重儿子 
    cnt++;
	dfn[u] = cnt; 
	res[cnt] = u;
	if(hson[u] == -1)return;
	top[hson[u]] = top[u];
    dfs2(hson[u],u); 
	for(int i = 0;i < G[u].size();i++){
		int v = G[u][i];
		if(v == p || hson[u] == v)continue;
		top[v] = v;
	    dfs2(v,u);
	} 
}
int lca(int u,int v){
	if(top[u] == top[v])return dep[u]<dep[v] ? u : v;//那个深度小就取哪个。
	if(dep[top[u]]<dep[top[v]]) return lca(u,fa[top[v]]);
	else return lca(fa[top[u]],v);
	//因为他是要跳到top的父亲上所以要比较两个top的深度; 
}
int path(int u,int v){
	return dep[u]+dep[v]-2*dep[lca(u,v)];
}
int num,ans;
set<int> s;
signed main(){
	ios::sync_with_stdio(0);
    int  n;
	cin>>n;
	for(int i = 1;i <= n-1;i++){
		int u,v,w;
		cin>>u>>v>>w;
		G[u].push_back(v);
		G[v].push_back(u);
		mp[{u,v}] = w;
		mp[{v,u}] = w;
	} 
	dfs1(1,0);
	dfs2(1,0);
	int q;
	cin>>q;
	while(q--){
		char c;
		cin>>c;
		if(c == '+'){
			int c;
			cin>>c;
			s.insert(dfn[c]);
			auto it = s.find(dfn[c]);
			if(s.size()==1)continue;
			int x,y;
			if(it==s.begin()){
				++it;
				x=*it,y=*(--s.end());
			}
			else{
				if(it==--s.end()){
					--it;
					x=*it,y=*(s.begin());
				}else{
					--it;x=*it;++it;++it;y=*it;
				}
			}
			ans=ans-path(res[x],res[y])+path(res[x],c)+path(c,res[y]);
		}
		else if(c == '-'){
			int c;cin>>c;
			int x,y;
			auto it=s.find(dfn[c]);
			if(s.size()==1){
				s.erase(it);
				continue;
			}
			if(it==s.begin()){
				++it;x=*it;y=*(--s.end());
			}else{
				if(it==(--s.end())){
					--it;x=*it;y=*(s.begin());
				}else{
					--it;x=*it;++it;++it;y=*it;
				}
			}
			ans=ans-path(res[x],c)-path(c,res[y])+path(res[x],res[y]);
			it=s.find(dfn[c]);
			s.erase(it);
		}
		else{
			cout<<ans/2<<endl;
		}
		
	}
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值