【BzoJ 4719】【Noip2016】【天天爱跑步】【lca】【方程移项】【桶排优化】

5 篇文章 0 订阅
3 篇文章 0 订阅


4719: [Noip2016]天天爱跑步

Time Limit: 40 Sec   Memory Limit: 512 MB
Submit: 1337   Solved: 451
[ Submit][ Status][ Discuss]

Description

小c同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。?天天爱跑步?是一个养成类游戏,需要
玩家每天按时上线,完成打卡任务。这个游戏的地图可以看作一一棵包含 N个结点和N-1 条边的树, 每条边连接两
个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从1到N的连续正整数。现在有个玩家,第个玩家的
起点为Si ,终点为Ti  。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发, 以每秒跑一条边的速度,
不间断地沿着最短路径向着自己的终点跑去, 跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树, 所以
每个人的路径是唯一的)小C想知道游戏的活跃度, 所以在每个结点上都放置了一个观察员。 在结点的观察员会选
择在第Wj秒观察玩家, 一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也理到达了结点J  。 小C想知道
每个观察员会观察到多少人?注意: 我们认为一个玩家到达自己的终点后该玩家就会结束游戏, 他不能等待一 段时
间后再被观察员观察到。 即对于把结点J作为终点的玩家: 若他在第Wj秒重到达终点,则在结点J的观察员不能观察
到该玩家;若他正好在第Wj秒到达终点,则在结点的观察员可以观察到这个玩家。

Input

第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。
接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。
接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。
接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证 。
1<=Si,Ti<=N,0<=Wj<=N

Output

输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。

Sample Input

6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6

Sample Output

2 0 0 1 1 1

HINT


对于1号点,W1=0,故只有起点为1号点的玩家才会被观察到,所以玩家1和玩家2被观察到,共2人被观察到。

对于2号点,没有玩家在第2秒时在此结点,共0人被观察到。

对于3号点,没有玩家在第5秒时在此结点,共0人被观察到。

对于4号点,玩家1被观察到,共1人被观察到。

对于5号点,玩家1被观察到,共1人被观察到。

对于6号点,玩家3被观察到,共1人被观察到

Source

[ Submit][ Status][ Discuss]


HOME Back

想到去年考noip时,当时写了一个暴力bfs就觉得是正解还真是naive呢;

本体由于n和m都会很大,所以如果是每个路径都暴力跑一次保证TLE没的说;

那么若想AC,肯定是经过预处理后直接求值,而且复杂度绝对不会超过 O(n*log(n));

那么我们细看一个观察员若是正好看到某个人跑过的时候需要满足的性质:
【1】deep[S] - deep[i] = t[i]

【2】lon - (deep[E] - deep[i]) = t[i] (S为一个跑步者的起点,E为终点;t[i]为站在点i上的观察员看人的时间)

那么就可以推导出式子

【1】deep[S] = t[i] +deep[i]

【2】lon - deep[E] = t[i] - deep[i]

那么我们不就可以发现一个点上可以看到的人数可以用两个桶表示;

那么怎么合并桶嘞?

我们发现经过一个点时,会有一些S或E使桶中的一些元素值增加,也会有一些LCA(S , E)(即一个路径不会再被经过时)使得一些元素值减少,所以我们可以不用去想合并桶,而是便利点时顺带加减(此处运用的是差分的思想)




那么在此整理总结一下:

本题由于如果是便利每条路径绝对会TLE,所以使我们想到可能是需要集中在一起处理;

由于推出式子:

【1】deep[S] - deep[i] = t[i]

【2】lon - (deep[E] - deep[i]) = t[i] 

然后由于我们看到这个等式同测的未知数并不统一,但是可以通过移向使其变得统一,所以得到等式:

【1】deep[S] = t[i] +deep[i](针对S -> LCA 路径上的点)

【2】lon - deep[E] = t[i] - deep[i](针对S -> LCA 路径上的点)

然后发现一个点上的观察员可以看到的跑步者都会有上述特性的其中一个,便想使用两个桶(一个针对S,一个针对E)将其储存起来(于是利用到了查分);

那么一个点的答案便是【桶1】[t[i] + deep[i]] +【桶2】[t[i] - deep[i] +3 * 10 ^5](由于是相减可能出现负数,所以需要将等式2左右两边同时加一个3 * 10 ^5以保证其肯定不为负,变为等式:lon - deep[E] + 3 * 10 ^5 = t[i] - deep[i] + 3 * 10 ^5)




注意:

(1)本题还有一个坑点在于对于一个路径的lca来说他同时在S -> LCA 和E -> LCA两条路径上,所以计算时会算重复,此处需要通过控制什么时候增加桶值什么时候减少桶值来解决此类问题

(2)虽然上面说了:答案便是【桶1】[t[i] + deep[i]] +【桶2】[t[i] - deep[i] +3 * 10 ^5]‘;但是因为算法实现时并非合并桶,所以当前的桶并不是你为根的子树的值,需要减去之前其他树上算出的值



代码如下:

#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 300001
using namespace std;
int n , m , w[M] , ans[M];
int ind[M] , nex[M * 2] , e[M * 2] ,cnt;
int st_up[M] , st_do[M * 2] , dip[M] , st[M][21];
vector<int> u_add[M] , u_div[M] , d_add[M] , d_div[M];
void add(int a , int b){
	nex[++cnt] = ind[a];
	e[ind[a] = cnt] = b;
}
void Init(int v , int f){
	dip[v] = dip[f] + 1;
	st[v][0] = f;
	for(int i = 1 ; i <= 20 ; ++i)
		st[v][i] = st[st[v][i - 1]][i - 1];
	for(int i = ind[v] ; i ; i = nex[i])
		if(e[i] != f)
			Init(e[i] , v);
}
int get_lca(int a , int b){
	if(dip[a] < dip[b])swap(a , b);
	int k = dip[a] - dip[b];
	for(int i = 20 ; i >= 0 ; --i)
		if((1 << i) <= k){
			a = st[a][i];
			k -= (1 << i);
		}
	if(a == b)return a;
	for(int i = 20 ; i >= 0 ; --i)
		if(st[a][i] != st[b][i]){
			a = st[a][i];
			b = st[b][i];
		}
	return st[a][0];
}
void dfs(int v , int f){
	int x = st_up[dip[v] + w[v]] , y = st_do[w[v] - dip[v] + M];
	for(int i = ind[v] ; i ; i = nex[i])
		if(e[i] != f)
			dfs(e[i] , v);
	for(int i = 0 ; i < u_add[v].size() ; ++i)
		st_up[u_add[v][i]]++;
	for(int i = 0 ; i < d_add[v].size() ; ++i)
		st_do[d_add[v][i]]++;
	for(int i = 0 ; i < u_div[v].size() ; ++i)
		st_up[u_div[v][i]]--;	
	ans[v] = st_up[dip[v] + w[v]] + st_do[w[v] - dip[v] + M] - x - y;	
	for(int i = 0 ; i < d_div[v].size() ; ++i)
		st_do[d_div[v][i]]--;	
	
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1 ; i < n ; ++i){
		int a , b;
		scanf("%d%d",&a,&b);
		add(a , b);
		add(b , a);
	}
	Init(1 , 0);
	for(int i = 1 ; i <= n ; ++i)scanf("%d",&w[i]);
	for(int i = 1 ; i <= m ; ++i){
		int st , ed;
		scanf("%d%d",&st,&ed);
		int lca = get_lca(st , ed);
		int lon = dip[st] + dip[ed] - dip[lca] * 2;
		u_add[st].push_back(dip[st]);
		u_div[lca].push_back(dip[st]);
		d_add[ed].push_back(lon - dip[ed] + M);
		d_div[lca].push_back(lon - dip[ed] + M);
	}
	dfs(1 , 0);
	for(int i = 1 ; i <= n ; ++i)printf("%d ",ans[i]);
}
/*
6 3
2 3
1 2 
1 4 
4 5 
4 6 
0 2 5 1 2 3 
1 5 
1 3 
2 6
*/



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值