HYSBZ 3991 寻宝游戏

题意:
小B最近正在玩一个寻宝游戏,这个游戏的地图中有N个村庄和N-1条道路,并且任何两个村庄之间有且仅有一条路径可达。游戏开始时,玩家可以任意选择一个村庄,瞬间转移到这个村庄,然后可以任意在地图的道路上行走,若走到某个村庄中有宝物,则视为找到该村庄内的宝物,直到找到所有宝物并返回到最初转移到的村庄为止。小B希望评测一下这个游戏的难度,因此他需要知道玩家找到所有宝物需要行走的最短路程。但是这个游戏中宝物经常变化,有时某个村庄中会突然出现宝物,有时某个村庄内的宝物会突然消失,因此小B需要不断地更新数据,但是小B太懒了,不愿意自己计算,因此他向你求助。为了简化问题,我们认为最开始时所有村庄内均没有宝物
题解:
①有一个比较常见的结论:
从一个点出发,经过指定的k个点再回到原点走的路径的长度是这k个点构成的生成树的边权和的两倍
②这道题如果把有宝藏的点当作特殊点,就相当于维护m棵虚树.
③每次重新建虚树是肯定不行的,每次只会修改一个点,插入和删除这个点一定会伴随着若干条路径的改变,找到这些路径就好了
④画图分析可以知道:
这些路径一定是当前操作的点x和DFS序在它前面的最后一个点L组成的
或者是和DFS序在它后面的第一个点R组成的
⑴如果L存在,那么这条路径就是L→x
⑵如果R存在路径R→x到x也会受到影响
⑶如果L和R同时存在,还要排除L→R的影响
⑷ans还要加上tmp是因为直接加dis会使DFS序最小的点x到DFS序最大的点y这条边x→y只算了一次,要加上另一次
⑤只需要记住每次加点还是删点都会改变若干条路径对答案的贡献,找到这些路径并修改贡献就好了.
⑥因为要每次要插入一个DFS序,找前驱后继,所以用set来维护

 

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;
typedef long long LL;
const int maxn=100000+10;
struct Node{
        int to,next,data;
}a[maxn<<1];
int n,m,head[maxn],cnt;
int prt[maxn],deep[maxn],size[maxn],son[maxn];
LL dis[maxn];
int Dfn[maxn],Time,top[maxn],pos[maxn];
bool flag[maxn];
LL ans=0;
set<int>S;
void Init();
void Insert(int,int,int);
void DFS1(int,int,int);
void DFS2(int,int);
int LCA(int,int);
LL Dis(int,int);
void Solve_Insert(int);
void Solve_Delte(int);
int main(){
        // freopen("in.cpp","r",stdin);
        Init();
        return 0;
}
void Init(){
        scanf("%d%d",&n,&m);
        int x,y,z;
        for(int i=1;i<n;i++){
                scanf("%d%d%d",&x,&y,&z);
                Insert(x,y,z);Insert(y,x,z);
        }
        DFS1(1,0,1);
        DFS2(1,1);
        S.insert(0);S.insert(n+1);
        while(m--){
                scanf("%d",&x);
                flag[x]=!flag[x];
                if(flag[x])Solve_Insert(x);
                else Solve_Delte(x);
        }
}
void Solve_Insert(int x){
        int L,R,prt1,prt2;LL tmp=0;
        S.insert(Dfn[x]);
        L=*--S.find(Dfn[x]);R=*++S.find(Dfn[x]);
        if(L>=1)ans+=Dis(pos[L],x);
        if(R<=n)ans+=Dis(pos[R],x);
        if(L>=1 && R<=n)ans-=Dis(pos[L],pos[R]);
        L=*++S.find(0),R=*--S.find(n+1);
        if(L>=1 && R<=n)
                tmp=Dis(pos[L],pos[R]);
        printf("%lld\n",tmp+ans);
}
void Solve_Delte(int x){
        int L,R,prt1,prt2;LL tmp=0;
        L=*--S.find(Dfn[x]);R=*++S.find(Dfn[x]);
        if(L>=1)ans-=Dis(pos[L],x);
        if(R<=n)ans-=Dis(pos[R],x);
        if(L>=1 && R<=n)ans+=Dis(pos[L],pos[R]);
        S.erase(Dfn[x]);
        L=*++S.find(0),R=*--S.find(n+1);
        if(L>=1 && R<=n)
                tmp=Dis(pos[L],pos[R]);
        printf("%lld\n",tmp+ans);
}
void Insert(int x,int y,int z){
        a[++cnt].to=y;
        a[cnt].next=head[x];
        a[cnt].data=z;
        head[x]=cnt;
}
#define y a[i].to
void DFS1(int x,int prt,int deep){
        ::deep[x]=deep;
        ::prt[x]=prt;
        ::size[x]=1;
        for(int i=head[x];i;i=a[i].next){
                if(y==prt)continue;
                dis[y]=dis[x]+a[i].data;
                DFS1(y,x,deep+1);
                size[x]+=size[y];
                if(size[y]>size[son[x]])son[x]=y;
        }
}
void DFS2(int x,int top){
        ::Dfn[x]=++Time;
        ::top[x]=top;
        ::pos[Time]=x;
        if(son[x])DFS2(son[x],top);
        for(int i=head[x];i;i=a[i].next){
                if(y==prt[x] || y==son[x])continue;
                DFS2(y,y);
        }
}
#undef y
int LCA(int x,int y){
        while(top[x]!=top[y]){
                if(deep[top[x]]<deep[top[y]])swap(x,y);
                x=prt[top[x]];
        }
        if(deep[x]<deep[y])return x;
        return y;
}
LL Dis(int x,int y){
        return dis[x]+dis[y]-2*dis[LCA(x,y)];
}

 

posted @ 2018-08-21 11:18  holy-unicorn  阅读(150)  评论(0编辑  收藏  举报