[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;
}
posted @ 2019-10-24 21:16  wsk1202  阅读(133)  评论(0编辑  收藏  举报