BZOJ3991【SDOI2015】寻宝游戏题解(虚树+set)

题目:BZOJ3991.
题目大意:给定一棵 n n n个点的树,现在要求维护一个点集,支持:
1.加入一个树上的点.
2.删除一个树上的点.
3.不规定顺序,询问经过点集中每一个点并回到起点的最短路径.
若操作次数为 m m m,则 1 ≤ n , m ≤ 1 0 5 1\leq n,m\leq 10^5 1n,m105.

显然地,最短路径肯定是从虚树的根开始依次dfs每一个点最后返回经过的总路程,也就是在原树上按照dfs序走最优.

问题变成动态维护虚树.考虑每次加一个点 x x x时,显然是插入到在dfs序意义下 x x x的前驱与后继之间,可以用一个set来维护这个东西.

再来考虑如何维护答案,每插入一个点 x x x,设 x x x的前驱为 l l l后继为 r r r,那么答案会减去 d i s ( l , r ) dis(l,r) dis(l,r)并加上 d i s ( x , l ) dis(x,l) dis(x,l) d i s ( x , r ) dis(x,r) dis(x,r).

同理,删除的时候就答案会减去 d i s ( x , l ) dis(x,l) dis(x,l) d i s ( x , r ) dis(x,r) dis(x,r)并加上 d i s ( l , r ) dis(l,r) dis(l,r).

然后就可以 O ( ( n + m ) log ⁡ n ) O((n+m)\log n) O((n+m)logn)解决这个问题了.

代码如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;
#define setit set<int>::iterator

const int N=100000;

int n,m;
struct side{
  int y,next,v;
}e[N*2+9];
int lin[N+9],cs;

void Ins(int x,int y,int v){e[++cs].y=y;e[cs].v=v;e[cs].next=lin[x];lin[x]=cs;}

struct node{
  int fa,dep,siz,son,top,dfs;
  LL dis;
}d[N+9];
int ord[N+9],co;

void Dfs_ord1(int k,int fa){
  d[k].fa=fa;
  d[k].dep=d[fa].dep+1;
  d[k].siz=1;
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^fa){
      d[e[i].y].dis=d[k].dis+e[i].v;
      Dfs_ord1(e[i].y,k);
      d[k].siz+=d[e[i].y].siz;
      if (d[e[i].y].siz>d[d[k].son].siz) d[k].son=e[i].y;
    }
}

void Dfs_ord2(int k,int top){
  d[k].top=top;
  d[k].dfs=++co;
  ord[co]=k;
  if (d[k].son) Dfs_ord2(d[k].son,top);
  for (int i=lin[k];i;i=e[i].next)
    if (e[i].y^d[k].fa&&e[i].y^d[k].son) Dfs_ord2(e[i].y,e[i].y);
}

int Query_lca(int x,int y){
  for (;d[x].top^d[y].top;x=d[d[x].top].fa)
    if (d[d[x].top].dep<d[d[y].top].dep) swap(x,y);
  return d[x].dep<d[y].dep?x:y;
}

LL Query_dis(int x,int y){return d[x].dis+d[y].dis-2*d[Query_lca(x,y)].dis;}

set<int>s;
LL ans;

void Insert(int k){
  s.insert(d[k].dfs);
  setit lit=s.find(d[k].dfs),rit=lit;--lit;++rit;
  if (lit==s.end()) --lit;
  if (rit==s.end()) rit=s.begin();
  ans-=Query_dis(ord[*lit],ord[*rit]);
  ans+=Query_dis(ord[*lit],k);
  ans+=Query_dis(ord[*rit],k);
}

void Erase(int k){
  setit lit=s.find(d[k].dfs),rit=lit;--lit;++rit;
  if (lit==s.end()) --lit;
  if (rit==s.end()) rit=s.begin();
  ans-=Query_dis(ord[*lit],k);
  ans-=Query_dis(ord[*rit],k);
  ans+=Query_dis(ord[*lit],ord[*rit]);
  s.erase(d[k].dfs);
}

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<n;++i){
  	int x,y,v;
  	scanf("%d%d%d",&x,&y,&v);
  	Ins(x,y,v);Ins(y,x,v); 
  }
}

Abigail work(){
  Dfs_ord1(1,0);
  Dfs_ord2(1,1);
}

Abigail getans(){
  for (int i=1;i<=m;++i){
  	int x;
  	scanf("%d",&x);
  	s.find(d[x].dfs)==s.end()?Insert(x):Erase(x);
  	printf("%lld\n",ans);
  }
}

int main(){
  into();
  work();
  getans();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值