【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, ...
随机推荐
- 一篇RxJava友好的文章(三)
组合操作符 继上一篇讲述了过滤操作符,这一篇讲述组合操作符,组合操作符可用于组合多个Observable.组合操作符相对于过滤操作符要复杂很多,也较难以理解,需要花费时间去看文档查资料,写demo才能 ...
- MacBookPro 存储空间优化
首先,打开电脑内的"终端"; 其次,逐条录入下面的命令行,执行完成后,再次查看您的储存空间,多少会有一些优化,具体会优化出多少储存空间因您日常使用而定(本人清出了5G 空间,还不错 ...
- 03-UI控件浏览
UI控件浏览 可能用得上的UI控件 为了便于开发者打造各式各样的优秀app,UIKit框架提供了非常多功能强大又易用的UI控件 下面列举一些在开发中可能用得上的UI控件(红色表明最常用,蓝色代表一般, ...
- LeetCode426.Convert Binary Search Tree to Sorted Doubly Linked List
题目 Convert a BST to a sorted circular doubly-linked list in-place. Think of the left and right point ...
- POJ2154 Color(Polya定理)
Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 11654 Accepted: 3756 Description Bead ...
- Linux平台下卸载MySQL的方法
转载自: https://www.cnblogs.com/taomylife/p/7234925.html Linux平台下卸载MySQL的方法: MySQL的安装主要有三种方式:二进制包安装.RPM ...
- datatable设置动态宽度,超过一定长度出现滚动条
获得宽度:var tableAutoWidth = $('.dataTable_wrapper').width();if (tableAutoWidth < 1200) { tableAutoW ...
- MySQL数据库 : 查询语句,连接查询及外键约束
查询指定字段 select 字段1,字段2 from 表名; 消除重复行(重复指的是结果集中的所有完全重复行) select distinct 字段1,字段2.. ...
- js三目运算符执行多个条件
三元运算符的结果语句可以执行多个操作,每个操作用逗号分隔就可以,例子如下: var a=1: a>5?(alert(1),alert(2)):(alert(3),alert(4))
- 使用命令行设置MySql编码格式
使用命令行设置MySql编码格式 1.登录mysql 2.输入 SHOW VARIABLES LIKE 'character_set_%'; 3.查看 value值是否为utf8,如果不是,则使用SE ...