BZOJ3991: [SDOI2015]寻宝游戏(set+lca / 虚树)
BZOJ2286: [Sdoi2011]消耗战##
Time Limit: 40 Sec
Memory Limit: 128 MBDescription###
小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/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。