4719: [Noip2016]天天爱跑步
Time Limit: 40 Sec Memory Limit: 512 MBSubmit: 1337 Solved: 451
[ Submit][ Status][ Discuss]
Description
Input
Output
输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。
Sample Input
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
Sample Output
HINT
Source
想到去年考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
*/