题目描述

给出一棵n个点的树,以及m次操作,每次操作从起点向终点以每秒一条边的速度移动(初始时刻为0),最后对于每个点询问有多少次操作在经过该点的时刻为某值。

输入

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

输出

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

样例输入

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

样例输出

2 0 0 1 1 1


题解

自己YY出的权值线段树合并

传说中的Noi没有p2016的神题。。。写出1个log的算法好像也不是很难。。。

先说下我的方法:

把每次操作拆成两部分:x->lca和lca->y,可以发现第一部分经过的点的 时刻+深度 是一个定值,第二部分经过的点的 时刻-深度 也是一个定值。所以问题转化为:给一条链上的每个点加1个定值、询问每个点某值的出现次数。

以第一种操作为例:由于只有1次总询问,因此可以差分打标记来实现修改,即在x处给权值标记+1,fa[lca]处给权值标记-1。然后处理询问时要求的就是子树的所有标记之和。可以使用线段树自底向上合并的方法来实现维护子树的所有标记之和。

总的时间复杂度为$O((n+m)\log n)$。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 300010
#define lson l , mid , ls[x]
#define rson mid + 1 , r , rs[x]
using namespace std;
int n , head[N] , to[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , log[N];
int w[N] , ra[N] , rb[N] , ls[N << 6] , rs[N << 6] , si[N << 6] , tot , ans[N];
void update(int p , int a , int l , int r , int &x)
{
if(!x) x = ++tot;
si[x] += a;
if(l == r) return;
int mid = (l + r) >> 1;
if(p <= mid) update(p , a , lson);
else update(p , a , rson);
}
int merge(int x , int y)
{
if(!x) return y;
if(!y) return x;
si[x] += si[y];
ls[x] = merge(ls[x] , ls[y]);
rs[x] = merge(rs[x] , rs[y]);
return x;
}
int query(int p , int l , int r , int x)
{
if(!x) return 0;
if(l == r) return si[x];
int mid = (l + r) >> 1;
if(p <= mid) return query(p , lson);
else return query(p , rson);
}
inline void add(int x , int y)
{
to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x)
{
int i;
for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa[x][0])
fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]);
}
inline int lca(int x , int y)
{
int i;
if(deep[x] < deep[y]) swap(x , y);
for(i = log[deep[x] - deep[y]] ; ~i ; i -- )
if(deep[x] - deep[y] >= (1 << i))
x = fa[x][i];
if(x == y) return x;
for(i = log[deep[x]] ; ~i ; i -- )
if(deep[x] >= (1 << i) && fa[x][i] != fa[y][i])
x = fa[x][i] , y = fa[y][i];
return fa[x][0];
}
void solve(int x)
{
int i;
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa[x][0])
solve(to[i]) , ra[x] = merge(ra[x] , ra[to[i]]) , rb[x] = merge(rb[x] , rb[to[i]]);
ans[x] = query(w[x] + deep[x] , 0 , n << 1 , ra[x]) + query(w[x] - deep[x] , -n , n , rb[x]);
}
int main()
{
int m , i , x , y , z;
scanf("%d%d" , &n , &m);
for(i = 2 ; i <= n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x) , log[i] = log[i >> 1] + 1;
dfs(1);
for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &w[i]);
for(i = 1 ; i <= m ; i ++ )
{
scanf("%d%d" , &x , &y) , z = lca(x , y);
update(deep[x] , 1 , 0 , n << 1 , ra[x]) , update(deep[x] , -1 , 0 , n << 1 , ra[fa[z][0]]);
update(deep[x] - 2 * deep[z] , 1 , -n , n , rb[y]) , update(deep[x] - 2 * deep[z] , -1 , -n , n , rb[z]);
}
solve(1);
for(i = 1 ; i < n ; i ++ ) printf("%d " , ans[i]);
printf("%d" , ans[n]);
return 0;
}

写完发现自己naive了。。。

事实上,求的是子树内的标记和。和是存在逆运算的(差),可以转化为 包含子树的部分与不包含子树的部分的差 来求出。

具体地,在递归子树之前的答案为a,递归子树之后的答案为b,那么b-a就是子树内的答案。

所以可以直接使用递归前后的差作为答案。此时不再需要单独维护子树,所以可以使用桶来维护答案,直接使用两次桶里的值作差即可。这个部分的复杂度降为了$O(n)$。

于是只需要在线性时间内求出每次操作的LCA即可做到线性复杂度。惜哉我不会写Tarjan离线LCA,于是这一部分的代码也没有补上。。。

所以贴一个倍增LCA+桶的代码吧(代码中细节还是不少的,比如桶的下标不能为负等等):

#include <cstdio>
#include <vector>
#define N 300010
#define lson l , mid , ls[x]
#define rson mid + 1 , r , rs[x]
using namespace std;
vector<int> va[N] , vb[N];
int n , head[N] , to[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , log[N];
int w[N] , ta[N << 1] , tb[N << 1] , ans[N];
inline void add(int x , int y)
{
to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x)
{
int i;
for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1];
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa[x][0])
fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]);
}
inline int lca(int x , int y)
{
int i;
if(deep[x] < deep[y]) swap(x , y);
for(i = log[deep[x] - deep[y]] ; ~i ; i -- )
if(deep[x] - deep[y] >= (1 << i))
x = fa[x][i];
if(x == y) return x;
for(i = log[deep[x]] ; ~i ; i -- )
if(deep[x] >= (1 << i) && fa[x][i] != fa[y][i])
x = fa[x][i] , y = fa[y][i];
return fa[x][0];
}
void solve(int x)
{
int i;
ans[x] -= ta[w[x] + deep[x] + 1] + tb[w[x] - deep[x] + n];
for(i = 0 ; i < (int)va[x].size() ; i ++ )
{
if(va[x][i] > 0) ta[va[x][i]] ++ ;
else ta[-va[x][i]] -- ;
}
for(i = 0 ; i < (int)vb[x].size() ; i ++ )
{
if(vb[x][i] > 0) tb[vb[x][i]] ++ ;
else tb[-vb[x][i]] -- ;
}
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa[x][0])
solve(to[i]);
ans[x] += ta[w[x] + deep[x] + 1] + tb[w[x] - deep[x] + n];
}
int main()
{
int m , i , x , y , z;
scanf("%d%d" , &n , &m);
for(i = 2 ; i <= n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x) , log[i] = log[i >> 1] + 1;
dfs(1);
for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &w[i]);
for(i = 1 ; i <= m ; i ++ )
{
scanf("%d%d" , &x , &y) , z = lca(x , y);
va[x].push_back(deep[x] + 1) , va[fa[z][0]].push_back(-deep[x] - 1);
vb[y].push_back(n + deep[x] - 2 * deep[z]) , vb[z].push_back(2 * deep[z] - deep[x] - n);
}
solve(1);
for(i = 1 ; i < n ; i ++ ) printf("%d " , ans[i]);
printf("%d" , ans[n]);
return 0;
}

【bzoj4719】[Noip2016]天天爱跑步 权值线段树合并的更多相关文章

  1. [NOIP2016]天天爱跑步(树上差分+线段树合并)

    将每个人跑步的路径拆分成x->lca,lca->y两条路径分别考虑: 对于在点i的观察点,这个人(s->t)能被观察到的充要条件为: 1.直向上的路径:w[i]=dep[s]-dep ...

  2. B20J_2733_[HNOI2012]永无乡_权值线段树合并

    B20J_2733_[HNOI2012]永无乡_权值线段树合并 Description:n座岛,编号从1到n,每座岛都有自己的独一无二的重要度,按照重要度可以将这n座岛排名,名次用1到 n来表示.某些 ...

  3. 【bzoj1977】[BeiJing2010组队]次小生成树 Tree 最小生成树+权值线段树合并

    题目描述 求一张图的严格次小生成树的边权和,保证存在. 输入 第一行包含两个整数N 和M,表示无向图的点数与边数. 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z ...

  4. 【bzoj2212】[Poi2011]Tree Rotations 权值线段树合并

    原文地址:http://www.cnblogs.com/GXZlegend/p/6826614.html 题目描述 Byteasar the gardener is growing a rare tr ...

  5. luogu3224 永无乡(动态开点,权值线段树合并)

    luogu3224 永无乡(动态开点,权值线段树合并) 永无乡包含 n 座岛,编号从 1 到 n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 到 n 来表示.某些 ...

  6. 【bzoj4399】魔法少女LJJ 并查集+权值线段树合并

    题目描述 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了LJJ感叹道“这里真是个迷人的绿色世界,空气清新.淡雅,到处散发着醉人的奶浆味: ...

  7. 【bzoj3307】雨天的尾巴 权值线段树合并

    题目描述 N个点,形成一个树状结构.有M次发放,每次选择两个点x,y,对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入 第一行数字N,M接下来 ...

  8. HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并)

    layout: post title: HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并) author: "luowentaoaa&quo ...

  9. BZOJ2733/LG3324 「HNOI2014」永无乡 权值线段树合并

    问题描述 BZOJ2733 LG3224 题解 对于每个结点建立一棵权值线段树. 查询操作就去查询第 \(k\) 大,合并操作就合并两颗权值线段树. 并查集维护连通性. 同时 STO hkk,zcr, ...

随机推荐

  1. ES5 与 ES6六大不同

    1.类Class 2.模块Module 导出变量 导出函数 导入 3.箭头函数 4.不再支持Mixins. 5.ES6不再支持自动绑定.

  2. hdu5691 Sitting in Line(状压dp)

    1 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> ...

  3. JS - 属性描述符各配置的默认值的注意事项

    通过字面量或者obj.x = 1;创建的属性 与 通过Object.defineProperty创建的属性,他们的属性描述符的默认值是不同的,前者都为true,后者都为false.

  4. mybatis的坑——不报错,就是不能committing,数据存不进数据库

    测试的时候会报空指针异常,在项目跑的时候会停止执行程序,不会出现异常. 经过一星期的排查与测试,最终找到错误,把mapper文件的映射属性名写错了. property属性名要与接收类的属性名保持一致. ...

  5. ubuntu16 升级pip3后报错File "/usr/bin/pip3", line 9, in <module> from pip import main ImportError: cannot import name 'main'

    问题:ubuntu16 执行pip3 install --upgrade pip之后,pip3执行出错. Traceback (most recent call last): File "/ ...

  6. 谷歌浏览器如何调试JS

    平常在开发过程中,经常会接触到前端页面.那么对于js的调试那可是家常便饭,谷歌浏览器是常用来调试JS代码的工具,本文主要介绍如何利用谷歌浏览器来调试JS代码,协助我们进行开发工作,加快开发效率. 1. ...

  7. 【nginx】root alias 区别,以及server root , location root 区别

    nginx-root-alias-详解 最近在研究前后端分离站点配置在同一域名下,发现root,alias有区别,而且所有的root如果都放置在location下面访问无效的问题,才有此总结,本文只是 ...

  8. 虚拟机桥接模式下多台Ubuntu16.04系统互相连接

    1.首先新建一个虚拟机并在该虚拟机上安装Ubuntu16.04系统.为这台虚拟机起名为Ubuntu3. 2.对Ubuntu3进行克隆,为新克隆生成的虚拟机起名为Ubuntu2.(这时我们会发现Ubun ...

  9. POJ:3262-Protecting the Flowers

    Protecting the Flowers Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 8606 Accepted: 347 ...

  10. android gridview 停止滚动

    http://blog.csdn.net/yaphetzhao/article/details/50544105 参考上面的博客,关键代码我就贴出来吧: public void stopGridVie ...