【bzoj4719】[Noip2016]天天爱跑步 权值线段树合并
题目描述
输入
输出
输出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]天天爱跑步 权值线段树合并的更多相关文章
- [NOIP2016]天天爱跑步(树上差分+线段树合并)
将每个人跑步的路径拆分成x->lca,lca->y两条路径分别考虑: 对于在点i的观察点,这个人(s->t)能被观察到的充要条件为: 1.直向上的路径:w[i]=dep[s]-dep ...
- B20J_2733_[HNOI2012]永无乡_权值线段树合并
B20J_2733_[HNOI2012]永无乡_权值线段树合并 Description:n座岛,编号从1到n,每座岛都有自己的独一无二的重要度,按照重要度可以将这n座岛排名,名次用1到 n来表示.某些 ...
- 【bzoj1977】[BeiJing2010组队]次小生成树 Tree 最小生成树+权值线段树合并
题目描述 求一张图的严格次小生成树的边权和,保证存在. 输入 第一行包含两个整数N 和M,表示无向图的点数与边数. 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z ...
- 【bzoj2212】[Poi2011]Tree Rotations 权值线段树合并
原文地址:http://www.cnblogs.com/GXZlegend/p/6826614.html 题目描述 Byteasar the gardener is growing a rare tr ...
- luogu3224 永无乡(动态开点,权值线段树合并)
luogu3224 永无乡(动态开点,权值线段树合并) 永无乡包含 n 座岛,编号从 1 到 n ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 n 座岛排名,名次用 1 到 n 来表示.某些 ...
- 【bzoj4399】魔法少女LJJ 并查集+权值线段树合并
题目描述 在森林中见过会动的树,在沙漠中见过会动的仙人掌过后,魔法少女LJJ已经觉得自己见过世界上的所有稀奇古怪的事情了LJJ感叹道“这里真是个迷人的绿色世界,空气清新.淡雅,到处散发着醉人的奶浆味: ...
- 【bzoj3307】雨天的尾巴 权值线段树合并
题目描述 N个点,形成一个树状结构.有M次发放,每次选择两个点x,y,对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. 输入 第一行数字N,M接下来 ...
- HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并)
layout: post title: HDU-6704 K-th occurrence (后缀自动机father树上倍增建权值线段树合并) author: "luowentaoaa&quo ...
- BZOJ2733/LG3324 「HNOI2014」永无乡 权值线段树合并
问题描述 BZOJ2733 LG3224 题解 对于每个结点建立一棵权值线段树. 查询操作就去查询第 \(k\) 大,合并操作就合并两颗权值线段树. 并查集维护连通性. 同时 STO hkk,zcr, ...
随机推荐
- vue面试常被问到的问题整理
1.Vue的双向数据绑定原理是什么? 答 : vue是采用数据劫持,并且使用发布-订阅者的开发模式.原理是观察者observer通过Object.defineProperty()来劫持到各个属性的ge ...
- Shell脚本使用汇总整理
Shell脚本使用汇总整理 一.Shell脚本常用的头部格式: 头部的作用就是告知linux此脚本的类型: 常用的头部格式如下:(/bin/bash,是bash的路径,如果不知道路径可以通过which ...
- Manacher算法:求解最长回文字符串,时间复杂度为O(N)
原文转载自:http://blog.csdn.net/yzl_rex/article/details/7908259 回文串定义:"回文串"是一个正读和反读都一样的字符串,比如&q ...
- #Python编程从入门到实践#第二章笔记
1.变量 (1)变量名只能包含字母.数字和下划线,不能包含空格 (2)不要将python关键字与函数名作为变量名 (3)简短有描述性,避免使用小写字母l和大写字母O (4)python 始终 ...
- POJ3682 概率DP
King Arthur's Birthday Celebration Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 3575 ...
- POJ 2079 最大三角形面积(凸包)
Triangle Description Given n distinct points on a plane, your task is to find the triangle that have ...
- python——“/”运算符和“//”运算符的区别
首先先看单斜杆的用法:举几个例子 >>> print(5/3),type(5/3)1.6666666666666667(None, <class 'float'>) &g ...
- 34-Cookie-based认证实现
新建MVC项目,然后用VSCode打开 dotnet new mvc --name MvcCookieAuthSample 在Controllers文件夹下新建AdminController.cs u ...
- 理解canvas路径
canvas路径和ps里面的路径差不多,在进行图形绘制时,先绘制出来图形的路径,然后再描边或者填充. canvas路径还有子路径的概念,在某一时刻,canvas之中只能有一条路径存在,Canvas规范 ...
- MAC中mongodb的连接遇到的问题及调试
今天在MAC环境下连接mongodb,遇到了一些报错,最终调试全部搞定.在此特做记录! 首先,mongod启动失败 上面有一句话是 exception in initAndListen: 20 Att ...