[SDOI2015]寻宝游戏
题意:有一棵有 \(n\) 个节点的树,带边权,某些点为关键点,初始没有关键点;有 \(q\) 个操作,每一次改变一个点的状态(关键点的变为非关键点,反之亦然),每次操作后输出所有关键点形成的极小联通子树的边权和的两倍。
由于图是一棵树,所以可以认识到从任一个关键点出发,按dfs序从小到大遍历所有其它关键点的路径都是一样的,都是最短距离。建树后跑一遍dfs,之后可以用倍增和lca求任意两点距离;问题就转化为本题的动态操作。
按照上文的方法,加入或删除一个关键点只要处理它自己、它按dfs序的前驱、后继点的距离即可,故使用一个支持插入,删除,查询前驱、后继的数据结构维护dfs序就可以得出答案了。
可以使用STL的set存关键点的dfn,但是由于本人set用不好,就直接写了一颗fhq Treap,常数略大……
代码如下
#include <cstdio>
#include <cstdlib>
#define inf (2100000000)
typedef long long ll;
inline ll rd(){
ll x=0,p=1;
char a=getchar();
while((a<48||a>57)&&a!='-')a=getchar();
if(a=='-')p=-p,a=getchar();
while(a>47&&a<58){
x=(x<<1)+(x<<3)+(a&15);
a=getchar();
}
return x*p;
}
inline void swap(int &x,int &y){int t=x;x=y;y=t;}
const int N=100002;
struct Edge{
int to,next;
ll w;
}edge[N<<1];
int head[N<<1],cnt=0;
int f[N][22],dep[N],dfn[N],tdfn[N],time,vis[N];
ll dis[N],ans=0;
int n,q;
inline void add(int f,int t,ll d){
edge[++cnt].next=head[f];
edge[cnt].to=t;
edge[cnt].w=d;
head[f]=cnt;
}
inline void dfs(int u,int ft){//dfs求出深度、父亲和距离
f[u][0]=ft,dep[u]=dep[f[u][0]]+1;
dfn[u]=++time,tdfn[time]=u;//记录dfn,另开一个数组记录dfn对应的点
for(int i=head[u];i;i=edge[i].next){
int v=edge[i].to;
if(v==ft)continue;
dis[v]=dis[u]+edge[i].w;
dfs(v,u);
}
}
inline int lca(int u,int v){//倍增lca
if(dep[u]<dep[v])swap(u,v);
for(int i=18;i>=0;i--)
if(dep[f[u][i]]>=dep[v])u=f[u][i];
if(u==v)return u;
for(int i=18;i>=0;i--)
if(f[u][i]!=f[v][i])
u=f[u][i],v=f[v][i];
return f[u][0];
}
inline ll getdis(int u,int v){return dis[u]+dis[v]-2*dis[lca(u,v)];}
//以下treap
int size[N],val[N],son[N][2],rnd[N],Size,root,x,y,z,sz;
inline void pushup(int rt){size[rt]=size[son[rt][0]]+size[son[rt][1]]+1;}
inline void split(int rt,int k,int &x,int &y){
if(!rt){x=y=0;return;}
else if(val[rt]<=k)x=rt,split(son[rt][1],k,son[rt][1],y);
else y=rt,split(son[rt][0],k,x,son[rt][0]);
pushup(rt);
}
inline int merge(int a,int b){
if(!a||!b)return a+b;
if(rnd[a]<rnd[b])return son[a][1]=merge(son[a][1],b),pushup(a),a;
else return son[b][0]=merge(a,son[b][0]),pushup(b),b;
}
inline int newnode(int k){size[++Size]=1,val[Size]=k,rnd[Size]=random();return Size;}
inline void insert(int k){
split(root,k,x,y);
root=merge(merge(x,newnode(k)),y);
}
inline void del(int k){
split(root,k,x,z),split(x,k-1,x,y);
y=merge(son[y][0],son[y][1]);
root=merge(merge(x,y),z);
}
inline int kth(int rt,int k){
while(1){
if(k<=size[son[rt][0]])rt=son[rt][0];
else if(k==size[son[rt][0]]+1)return rt;
else k-=size[son[rt][0]]+1,rt=son[rt][1];
}
return -1;
}
inline int pre(int k){
split(root,k-1,x,y);
int ans=val[kth(x,size[x])];
root=merge(x,y);
return ans;
}
inline int suc(int k){
split(root,k,x,y);
int ans=val[kth(y,1)];
root=merge(x,y);
return ans;
}
int main(){
n=rd(),q=rd();
for(int i=1;i<n;i++){
int u=rd(),v=rd();ll w=rd();
add(u,v,w),add(v,u,w);
}
dfs(1,0);
for(int j=1;j<=18;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
insert(inf),insert(-inf),sz=2;//先插入inf防爆,记录真实的数据个数
while(q--){
int u=rd();
if(!vis[u])insert(dfn[u]),sz++;//若原先没有这个点,先插入再计算
int p=pre(dfn[u]),s=suc(dfn[u]);
if(p==-inf)p=val[kth(root,sz-1)];//如果超过,找最(前)后的点
if(s== inf)s=val[kth(root,2)];
if(vis[u])del(dfn[u]),sz--;//若原先有这个点,先计算再删除
ll d=getdis(u,tdfn[p])+getdis(u,tdfn[s])-getdis(tdfn[p],tdfn[s]);//关于贡献的计算,画出一棵树推一下即可得到一个点的贡献即可得到式子
if(vis[u])vis[u]=0,ans-=d;
else vis[u]=1,ans+=d;//更新答案
printf("%lld\n",ans);
}
return 0;
}