[bzoj3991][SDOI2015]寻宝游戏 lca+set+dfs序

8 篇文章 0 订阅
4 篇文章 0 订阅

3991: [SDOI2015]寻宝游戏

Time Limit: 40 Sec   Memory Limit: 128 MB
Submit: 1379   Solved: 671
[ Submit][ Status][ Discuss]

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

Source

这道题知道怎么做后是比较简单的
开始我想的是树链剖分+01线段树,再用set维护所有数
但是那个set是没有排序依据的
在这道题中按dfs序排序
首先它要求的是从一些点的一个点到点集中所有点再回来的最小距离
首先我们要知道我们一个点到另一个点最短路径唯一确定
然后我们假想从一个节点出发是要回到这个节点的,
所以一条要经过的边最少是要走两次的,且必须经过
那么如何让我们每一条边之走两次呢?
我们想到在dfs一棵树的时候,我们每一个节点求一个in和out
也就是说,我们每一条边只走了两次dfs了一棵树
那么我们使dfs序排在第一的点作为起点,dfs序在最后的点作为最后一个被访问的点
set用dfs序作为顺序
然后我们维护的答案就是起点经过所有点到终点的距离,再加上起点到终点的距离
注意这不是唯一方案,只是最优方案之一
我把深度写成16还A了
数据太水了
数据太水了
数据太水了
#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;
const int N = 100000 + 5;
typedef long long ll;
int cnt,n,m,sz,opt; bool vis[N];
int last[N],dep[N],anc[N][20],in[N],inv_in[N],siz[N];
ll d[N],ans;
struct Edge{ int to, next, v; }e[N<<1];
void insert( int u, int v, int w ){
	e[++cnt].to = v; e[cnt].next = last[u]; e[cnt].v = w; last[u] = cnt;
	e[++cnt].to = u; e[cnt].next = last[v]; e[cnt].v = w; last[v] = cnt;
}
void dfs( int x, int f ){
	siz[x] = 1; in[x] = ++sz; inv_in[sz] = x;
	for( int p = 1; p <= 16; p++ ) anc[x][p] = anc[anc[x][p-1]][p-1];
	for( int i = last[x]; i; i = e[i].next )
		if( e[i].to ^ f ){
			dep[e[i].to] = dep[x]+1;
			d[e[i].to] = d[x] + e[i].v;
			anc[e[i].to][0] = x;
			dfs( e[i].to, x );
			siz[x] += siz[e[i].to];
		}
}
int lca( int x, int y ){
	if( dep[x] < dep[y] ) swap(x,y);
	int t = dep[x] - dep[y];
	for( int p = 0; p <= 16; p++ )
		if( (1<<p)&t )
			x = anc[x][p];
	if( x == y ) return x;
	for( int p = 16; p >= 0; p-- )
		if( anc[x][p] != anc[y][p] )
			x = anc[x][p], y = anc[y][p];
	return anc[x][0];
}
ll dis( int x, int y ){
	return d[x] + d[y] - 2*d[lca( x, y )];
}
set <int> q;
set <int> :: iterator it;
int next( int x ){
	it = q.find(in[x]);
	return ++it == q.end() ? 0 : inv_in[*it];
}
int pre( int x ){
	it = q.find(in[x]);
	return it == q.begin() ? 0 : inv_in[*--it];
}
void set_erase( int x ){
	int pr = pre(x), ne = next(x);
	q.erase(in[x]);
	if( pr ) ans -= dis( pr, x );
	if( ne ) ans -= dis( x, ne );
	if( pr && ne ) ans += dis( pr, ne );
}
void set_insert( int x ){
	q.insert(in[x]);
	int pr = pre(x), ne = next(x);
	if( pr ) ans += dis( pr, x );
	if( ne ) ans += dis( x, ne );
	if( pr && ne ) ans -= dis( pr, ne );
}
int main(){
	scanf( "%d%d", &n, &m );
	for( int i = 1,u,v,w; i < n; i++ ){
		scanf( "%d%d%d", &u, &v, &w );
		insert( u, v, w );
	}
	dfs( 1, 0 );
	while( m-- ){
		int x;
		scanf( "%d", &x );
		if( vis[x] ) set_erase(x), vis[x] = 0;
		else set_insert(x), vis[x] = 1;
		if( !q.size() ) puts("0");
		else printf( "%lld\n", ans + dis( inv_in[*q.begin()], inv_in[*--q.end()] ) );
	}
	return 0;
}


数据太水了
数据太水了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值