• 【BZOJ3991】[SDOI2015]寻宝游戏 树链的并+set


    【BZOJ3991】[SDOI2015]寻宝游戏

    Description

     小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

    题解:从前有一个神奇的序列,它叫DFS序,它有一个神奇的性质,就是两点间LCA的深度=两点在DFS序上的区间中深度的最小值。从前有一堆树链,它们跑到了DFS序上,并按DFS序排成了一列,它们的并就是每个树链的长度-相邻两个树链的LCA到根的路径的长度。

    这个性质其实很好理解,也很好证吧~

    所以我们用set维护DFS序,每加入一个点就找出它在DFS序上的前驱后继,计算树链的并的变化长度,删除时类似。不过由于可以从任意一个节点出发,所以总长度应该减去所有点的LCA到根的路径长度(也就是DFS序最小的和最大的点的LCA),答案就是总长度*2

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <set>
    using namespace std;
    const int maxn=100010;
    typedef long long ll;
    ll sum;
    int n,m,lgn,cnt,tot;
    int to[maxn<<1],next[maxn<<1],head[maxn],fa[maxn][20],p[maxn],q[maxn],dep[maxn],ins[maxn];
    set<int> s;
    set<int>::iterator it;
    ll val[maxn<<1],len[maxn];
    int rd()
    {
    	int ret=0,f=1;	char gc=getchar();
    	while(gc<'0'||gc>'9')	{if(gc=='-')f=-f;	gc=getchar();}
    	while(gc>='0'&&gc<='9')	ret=ret*10+gc-'0',gc=getchar();
    	return ret*f;
    }
    void add(int a,int b,int c)
    {
    	to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++;
    }
    void dfs(int x)
    {
    	q[++p[0]]=x,p[x]=p[0];
    	for(int i=head[x];i!=-1;i=next[i])
    		if(to[i]!=fa[x][0])
    			fa[to[i]][0]=x,len[to[i]]=len[x]+val[i],dep[to[i]]=dep[x]+1,dfs(to[i]);
    }
    int lca(int a,int b)
    {
    	if(dep[a]<dep[b])	swap(a,b);
    	int i;
    	for(i=lgn;i>=0;i--)	if(dep[fa[a][i]]>=dep[b])	a=fa[a][i];
    	if(a==b)	return a;
    	for(i=lgn;i>=0;i--)	if(fa[a][i]!=fa[b][i])	a=fa[a][i],b=fa[b][i];
    	return fa[a][0];
    }
    int main()
    {
    	n=rd(),m=rd();
    	int i,j,a,b,c;
    	memset(head,-1,sizeof(head));
    	for(i=1;i<n;i++)	a=rd(),b=rd(),c=rd(),add(a,b,c),add(b,a,c);
    	dep[1]=1,dfs(1);
    	for(j=1;(1<<j)<=n;j++)
    		for(lgn=j,i=1;i<=n;i++)
    			fa[i][j]=fa[fa[i][j-1]][j-1];
    	for(i=1;i<=m;i++)
    	{
    		a=rd();
    		if(!ins[a])
    		{
    			it=s.upper_bound(p[a]),b=c=0;
    			if(it!=s.end())	c=q[*it];
    			if(it!=s.begin())	it--,b=q[*it];
    			if(b&&c)	sum+=len[lca(b,c)];
    			if(b)	sum-=len[lca(a,b)];
    			if(c)	sum-=len[lca(a,c)];
    			tot++,ins[a]=1,sum+=len[a],s.insert(p[a]);
    		}
    		else
    		{
    			s.erase(p[a]),it=s.upper_bound(p[a]),b=c=0;
    			if(it!=s.end())	c=q[*it];
    			if(it!=s.begin())	it--,b=q[*it];
    			if(b&&c)	sum-=len[lca(b,c)];
    			if(b)	sum+=len[lca(a,b)];
    			if(c)	sum+=len[lca(a,c)];
    			tot--,ins[a]=0,sum-=len[a];
    		}
    		if(tot==1||tot==0)
    		{
    			printf("0
    ");
    			continue;
    		}
    		it=s.begin(),b=q[*it],it=s.end(),--it,c=q[*it];
    		printf("%lld
    ",2*(sum-len[lca(b,c)]));
    	}
    	return 0;
    }
  • 相关阅读:
    jmeter接口自动化-读取CSV文件执行测试用例
    文件流下载excel表格
    如何查看死锁的表
    学习笔记
    当你需要验证数组是否都是0
    实验二
    centos8 https访问报错
    Linux命令常用搜集持续更新
    一文搞懂C语言中指针、数组、指针数组、数组指针、函数指针、指针函数
    11
  • 原文地址:https://www.cnblogs.com/CQzhangyu/p/7072441.html
Copyright © 2020-2023  润新知