BZOJ3991: [SDOI2015]寻宝游戏(set+lca / 虚树)


BZOJ2286: [Sdoi2011]消耗战##

  Time Limit: 40 Sec
  Memory Limit: 128 MB

Description###

   小B最近正在玩一个寻宝游戏,这个游戏的地图中有N个村庄和N-1条道路,并且任何两个村庄之间有且仅有一条路径可达。游戏开始时,玩家可以任意选择一个村庄,瞬间转移到这个村庄,然后可以任意在地图的道路上行走,若走到某个村庄中有宝物,则视为找到该村庄内的宝物,直到找到所有宝物并返回到最初转移到的村庄为止。小B希望评测一下这个游戏的难度,因此他需要知道玩家找到所有宝物需要行走的最短路程。但是这个游戏中宝物经常变化,有时某个村庄中会突然出现宝物,有时某个村庄内的宝物会突然消失,因此小B需要不断地更新数据,但是小B太懒了,不愿意自己计算,因此他向你求助。为了简化问题,我们认为最开始时所有村庄内均没有宝物
 

Input###

   第一行,两个整数N、M,其中M为宝物的变动次数。
  接下来的N-1行,每行三个整数x、y、z,表示村庄x、y之间有一条长度为z的道路。
  接下来的M行,每行一个整数t,表示一个宝物变动的操作。若该操作前村庄t内没有宝物,则操作后村庄内有宝物;若该操作前村庄t内有宝物,则操作后村庄内没有宝物。
 

Output###

   M行,每行一个整数,其中第i行的整数表示第i次操作之后玩家找到所有宝物需要行走的最短路程。若只有一个村庄内有宝物,或者所有村庄内都没有宝物,则输出0。
 

Sample Input###

  4 5
  1 2 30
  2 3 50
  2 4 60
  2
  3
  4
  2
  1
 

Sample Output###

  0
  100
  220
  220
  280
    

HINT

   1<=N<=100000
   1<=M<=100000
   对于全部的数据,1<=z<=10^9
  

题目地址:  BZOJ3991: [SDOI2015]寻宝游戏

题目大意: 已经很简洁了

题解:

  本来是想做虚树来做这道题的
  突然发现 set+lca 炒鸡简单
  
  用一个 set 维护 dfn
  插入操作:我们可以将一点 x 插入到 set 中,将上一次的答案加上 x 点到其前驱以及后继点的路径长,再减去该两点之间的路径长。
  \(insert_{ans}(x)=ans_{last}-dis(before,after)+dis(x,before)+dis(x,after)\)
  
  删除操作:我们可以将上一次答案加上待删除的点x的前驱和后继之间的路径长,再减去分别到前驱后后继的路径。
  \(erase_{ans}(x)=ans_{last}+dis(before,after)-dis(x,before)-dis(x,after)\)
  
  其中dis为
  \(dis(x,y)=dis\bigr(LCA(x,y),x\bigl)+dis\bigr(LCA(x,y),y\bigl)\)
  我们可以用树剖或倍增求lca
  再加上最后一个到第一个的距离就完事了
  参考 https://www.luogu.org/problemnew/solution/P3320


set + 倍增lca

#include <cstdio> 
#include <algorithm>
#include <set>
#define ll long long
using namespace std;
const int N=1e5+5;
int n,Q,cnt;
int last[N],fa[N][18];
ll num,ans,dis[N][18];
bool mark[N];
set<int> q;
struct edge{
	int to,val,next;
}e[N<<1];
inline void add_edge(int u,int v,int w){
	e[++cnt]=(edge){v,w,last[u]};last[u]=cnt;
	e[++cnt]=(edge){u,w,last[v]};last[v]=cnt;
}
int ind,pos[N],sop[N],dep[N];
void dfs(int u){
	pos[u]=++ind;
	sop[ind]=u;
	for(int i=last[u];i;i=e[i].next){
		int v=e[i].to;
		if(v==fa[u][0])continue;
		fa[v][0]=u;dis[v][0]=e[i].val;
		dep[v]=dep[u]+1;
		dfs(v);
	}
}
ll lca(int a,int b){
	ll res=0;
	if(dep[a]<dep[b])swap(a,b);
	for(int i=17;i>=0;i--)
		if(dep[fa[a][i]]>=dep[b]){
			res+=dis[a][i];
			a=fa[a][i];
		}
	for(int i=17;i>=0;i--)
		if(fa[a][i]!=fa[b][i]){
			res+=dis[a][i]+dis[b][i];
			a=fa[a][i];b=fa[b][i];
		}
	if(a==b)return res;
	res+=dis[a][0]+dis[b][0];
	return res;
}
ll Dis(int a,int b){
	a=sop[a];b=sop[b];
	return lca(a,b);
}
void Insert(int x){
	mark[x]=1;x=pos[x];
	q.insert(x);
	int lst=*(--q.find(x));
	int nxt=*(++q.find(x));
	if(lst>=1)ans+=Dis(lst,x);
	if(nxt<=n)ans+=Dis(x,nxt);
	if(1<=lst && nxt<=n)ans-=Dis(lst,nxt);
}
void Erase(int x){
	mark[x]=0;x=pos[x];
	int lst=*(--q.find(x));
	int nxt=*(++q.find(x));
	if(lst>=1)ans-=Dis(lst,x);
	if(nxt<=n)ans-=Dis(x,nxt);
	if(1<=lst && nxt<=n)ans+=Dis(lst,nxt);
	q.erase(x);
}
int main(){
	scanf("%d%d",&n,&Q);
	for(int i=1;i<n;i++){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
	}
	dep[1]=1;
	dfs(1);
	for(int j=1;j<=17;j++)
		for(int i=1;i<=n;i++){
			fa[i][j]=fa[fa[i][j-1]][j-1];
			dis[i][j]=dis[i][j-1]+dis[fa[i][j-1]][j-1];
		}
	q.insert(0);
	q.insert(n+1);
	while(Q--){
		int x;
		scanf("%d",&x);
		if(!mark[x])Insert(x);
		else Erase(x);
		int Sta=*(++q.find(0));
		int End=*(--q.find(n+1));
		if(1<=Sta && End<=n)num=Dis(Sta,End);
		printf("%lld\n",ans+num);
	}
	return 0;
}

set + 树剖lca


虚树



  作者:skl_win
  出处:https://www.cnblogs.com/shaokele/
  本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

posted @ 2018-08-21 10:57  skl_win  阅读(147)  评论(0编辑  收藏  举报
Live2D