2019南昌邀请赛网络预选赛 J.Distance on the tree(树链剖分)
题意:
给出一棵树,每条边都有权值;
给出 m 次询问,每次询问有三个参数 u,v,w ,求节点 u 与节点 v 之间权值 ≤ w 的路径个数;
题解:
昨天再打比赛的时候,中途,凯少和我说,这道题,一眼看去,就是树链剖分,然鹅,太久没写树链剖分的我一时也木有思路;
今天上午把树链剖分温习了一遍,做了个模板题;
下午再想了一下这道题,思路喷涌而出............
首先,介绍一下相关变量:
int fa[maxn];//fa[u]:u的父节点
int son[maxn];//son[u]:u的重儿子
int dep[maxn];//dep[u]:u的深度
int siz[maxn];//siz[u]:以u为根的子树节点个数
int tid[maxn];//tid[u]:u在线段树中的位置
int top[maxn];//top[u]:u所在重链的祖先节点
int e[maxn][];//e[i][0]与e[i][1]有条权值为e[i][2]的边
vector<int >v[maxn<<];//v[i]:存储线段树中i号节点的所有边的权值
(树链剖分,默认来看这篇博客的都会辽,逃)
下面重点介绍一下v[]的作用(将样例2中的权值改为了10):

由树链剖分可知(图a,紫色部分代表重链)
tid[1]=1,tid[3]=2,tid[5]=3;
tid[2]=4,tid[4]=5;
那么,线段树维护啥呢?
struct SegmentTree
{
int l,r;
int mid()
{
return l+((r-l)>>);
}
}segTree[maxn<<];
vector<int >v[maxn<<];//v[i]:存储线段树中i号节点的所有边的权值
对于我而言,此次线段树,主要维护节点 i 的左右区间[l,r],重点是 v[] 中维护的东西;
首先将边权存到线段树中,如何存呢?
对于边 u,v,w ,(假设 fa[v]=u),将 w 存在 v[ tid[ v ] ]中;
看一下Update()函数:
//将节点x在线段树中对应的pos位置的v中加入val
void Update(int x,int val,int pos)
{
if(segTree[pos].l == segTree[pos].r)
{
v[pos].push_back(val);//val加入到v[pos]中
return ;
}
int mid=segTree[pos].mid();
if(x <= mid)
Update(x,val,ls(pos));
else
Update(x,val,rs(pos));
}
例如上图b:
①-② : 10 ,调用函数Update(tid[2],10,1) ⇔ v[tid[2]].push_back(10)
①-③ : 10 ,调用函数Update(tid[3],10,1) ⇔ v[tid[3]].push_back(10)
②-④ : 10 ,调用函数Update(tid[4],10,1) ⇔ v[tid[4]].push_back(10)
③-⑤ : 10 ,调用函数Update(tid[5],10,1) ⇔ v[tid[5]].push_back(10)
线段树中的节点9中的v存储一个10
线段树中的节点5中的v存储一个10
线段树中的节点6中的v存储一个10
线段树中的节点7中的v存储一个10
这个就是Update()函数的作用;
接下来的pushUp()函数很重要:
void pushUp(int pos)
{
if(segTree[pos].l == segTree[pos].r)
return; pushUp(ls(pos));
pushUp(rs(pos)); //将ls(pos),rs(pos)中的元素存储到pos中
for(int i=;i < v[ls(pos)].size();++i)
v[pos].push_back(v[ls(pos)][i]);
for(int i=;i < v[rs(pos)].size();++i)
v[pos].push_back(v[rs(pos)][i]);
sort(v[pos].begin(),v[pos].end());//升序排列
}
调用pushUp(1),将所有的pos 的 ls(pos),rs(pos) 节点信息更新到pos节点;
调用完这个函数后,你会发现:
v[1]:10,10,10,10([1,5]中的所有节点到其父节点的权值,根节点为null)
v[2]:10,10([1,3]中的所有节点到其父节点的权值)
v[3]:10,10([4,5]中的所有节点到其父节点的权值)
v[4]:10([1,2]中的所有节点到其父节点的权值)
v[5]:10([3,3]中的所有节点到其父节点的权值)
v[6]:10([4,4]中的所有节点到其父节点的权值)
v[7]:10([5,5]中的所有节点到其父节点的权值)
v[8]:null(根节点为null)
v[9]:10([2,2]中的所有节点到其父节点的权值)
你会发现,v[i]中存的值就是[ tree[i].l , tree[i].r ]中所有节点与其父节点的权值;
接下来就是询问操作了:
int BS(int pos,int w)
{
int l=-,r=v[pos].size();
while(r-l > )
{
int mid=l+((r-l)>>);
if(v[pos][mid] <= w)
l=mid;
else
r=mid;
}
return l+;
}
int Query(int l,int r,int pos,int w)
{
if(v[pos][] > w)//当前区间的如果最小的值要 > w,直接返回0
return ;
if(segTree[pos].l == l && segTree[pos].r == r)
return BS(pos,w);//二分查找pos区间值 <= w 得个数(还记得pushUp()中的sort函数么? int mid=segTree[pos].mid();
if(r <= mid)
return Query(l,r,ls(pos),w);
else if(l > mid)
return Query(l,r,rs(pos),w);
else
return Query(l,mid,ls(pos),w)+Query(mid+,r,rs(pos),w);
}
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define INF 0x3f3f3f3f
#define mem(a,b) memset(a,b,sizeof(a))
const int maxn=1e5+; int n,m;
int fa[maxn];//fa[u]:u的父节点
int son[maxn];//son[u]:u的重儿子
int dep[maxn];//dep[u]:u的深度
int siz[maxn];//siz[u]:以u为根的子树节点个数
int tid[maxn];//tid[u]:u在线段树中的位置
int top[maxn];//top[u]:u所在重链的祖先节点
int e[maxn][];//e[i][0]与e[i][1]有条权值为e[i][2]的边
vector<int >v[maxn<<];//v[i]:存储线段树中i号节点的所有边的权值
int num;
int head[maxn];
struct Edge
{
int to;
int w;
int next;
}G[maxn<<];
void addEdge(int u,int v,int w)
{
G[num].to=v;
G[num].w=w;
G[num].next=head[u];
head[u]=num++;
}
struct SegmentTree
{
int l,r;
int mid()
{
return l+((r-l)>>);
}
}segTree[maxn<<];
void DFS1(int u,int f,int depth)
{
fa[u]=f;
son[u]=-;
siz[u]=;
dep[u]=depth;
for(int i=head[u];~i;i=G[i].next)
{
int v=G[i].to;
if(v == f)
continue;
DFS1(v,u,depth+); siz[u] += siz[v]; if(son[u] == - || siz[v] > siz[son[u]])
son[u]=v;
}
}
void DFS2(int u,int anc,int &k)
{
top[u]=anc;
tid[u]=++k;
if(son[u] == -)
return ;
DFS2(son[u],anc,k); for(int i=head[u];~i;i=G[i].next)
{
int v=G[i].to;
if(v != fa[u] && v != son[u])
DFS2(v,v,k);
}
}
void pushUp(int pos)
{
if(segTree[pos].l == segTree[pos].r)
return; pushUp(ls(pos));
pushUp(rs(pos)); //将ls(pos),rs(pos)中的元素存储到pos中
for(int i=;i < v[ls(pos)].size();++i)
v[pos].push_back(v[ls(pos)][i]);
for(int i=;i < v[rs(pos)].size();++i)
v[pos].push_back(v[rs(pos)][i]);
sort(v[pos].begin(),v[pos].end());//升序排列
}
void buildSegTree(int l,int r,int pos)
{
segTree[pos].l=l;
segTree[pos].r=r;
if(l == r)
return ; int mid=l+((r-l)>>);
buildSegTree(l,mid,ls(pos));
buildSegTree(mid+,r,rs(pos));
}
//将节点x在线段树中对应的pos位置的v中加入val
void Update(int x,int val,int pos)
{
if(segTree[pos].l == segTree[pos].r)
{
v[pos].push_back(val);//val加入到v[pos]中
return ;
}
int mid=segTree[pos].mid();
if(x <= mid)
Update(x,val,ls(pos));
else
Update(x,val,rs(pos));
}
int BS(int pos,int w)
{
int l=-,r=v[pos].size();
while(r-l > )
{
int mid=l+((r-l)>>);
if(v[pos][mid] <= w)
l=mid;
else
r=mid;
}
return l+;
}
int Query(int l,int r,int pos,int w)
{
if(v[pos][] > w)//当前区间的如果最小的值要 > w,直接返回0
return ;
if(segTree[pos].l == l && segTree[pos].r == r)
return BS(pos,w);//二分查找pos区间值 <= w 得个数(还记得pushUp()中的sort函数么? int mid=segTree[pos].mid();
if(r <= mid)
return Query(l,r,ls(pos),w);
else if(l > mid)
return Query(l,r,rs(pos),w);
else
return Query(l,mid,ls(pos),w)+Query(mid+,r,rs(pos),w);
}
int Find(int u,int v,int w)//查询节点u到节点v之间权值小于等于w得路径个数
{
int ans=;
int topU=top[u];
int topV=top[v];
while(topU != topV)
{
if(dep[topU] > dep[topV])
{
swap(u,v);
swap(topU,topV);
}
ans += Query(tid[top[v]],tid[v],,w);
v=fa[topV];
topV=top[v];
}
if(u == v)
return ans;
if(dep[u] > dep[v])
swap(u,v);
return ans+Query(tid[son[u]],tid[v],,w);
}
void Solve()
{
DFS1(,,);
int k=;
DFS2(,,k); buildSegTree(,k,); for(int i=;i < n;++i)
{
if(dep[e[i][]] > dep[e[i][]])
swap(e[i][],e[i][]);//令fa[e[i][1]] = e[i][0],方便更新操作
Update(tid[e[i][]],e[i][],);//将e[i][2]加入到tid[e[i][1]]中
}
pushUp();//更新线段树中所有的pos for(int i=;i <= m;++i)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
printf("%d\n",Find(u,v,w));
}
}
void Init()
{
num=;
mem(head,-);
for(int i=;i < *maxn;++i)
v[i].clear();
}
int main()
{
// freopen("C:\\Users\\hyacinthLJP\\Desktop\\in&&out\\contest","r",stdin);
while(~scanf("%d%d",&n,&m))
{
Init();
for(int i=;i < n;++i)
{
scanf("%d%d%d",e[i]+,e[i]+,e[i]+);
addEdge(e[i][],e[i][],e[i][]);
addEdge(e[i][],e[i][],e[i][]);
}
Solve();
}
return ;
}

2019南昌邀请赛网络预选赛 J.Distance on the tree(树链剖分)的更多相关文章
- 2019南昌邀请赛网络赛:J distance on the tree
1000ms 262144K DSM(Data Structure Master) once learned about tree when he was preparing for NOIP(N ...
- 2019年ICPC南昌网络赛 J. Distance on the tree 树链剖分+主席树
边权转点权,每次遍历到下一个点,把走个这条边的权值加入主席树中即可. #include<iostream> #include<algorithm> #include<st ...
- 计蒜客 2019南昌邀请网络赛J Distance on the tree(主席树)题解
题意:给出一棵树,给出每条边的权值,现在给出m个询问,要你每次输出u~v的最短路径中,边权 <= k 的边有几条 思路:当时网络赛的时候没学过主席树,现在补上.先树上建主席树,然后把边权交给子节 ...
- 2019南昌邀请赛网络预选赛 M. Subsequence
传送门 题意: 给出一个只包含小写字母的串 s 和n 个串t,判断t[i]是否为串 s 的子序列: 如果是,输出"YES",反之,输出"NO": 坑点: 二分一 ...
- 2019南昌邀请赛网络预选赛 I. Max answer(单调栈+暴力??)
传送门 题意: 给你你一序列 a,共 n 个元素,求最大的F(l,r): F(l,r) = (a[l]+a[l+1]+.....+a[r])*min(l,r); ([l,r]的区间和*区间最小值,F( ...
- 计蒜客 38229.Distance on the tree-1.树链剖分(边权)+可持久化线段树(区间小于等于k的数的个数)+离散化+离线处理 or 2.树上第k大(主席树)+二分+离散化+在线查询 (The Preliminary Contest for ICPC China Nanchang National Invitational 南昌邀请赛网络赛)
Distance on the tree DSM(Data Structure Master) once learned about tree when he was preparing for NO ...
- 南昌网络赛J. Distance on the tree 树链剖分
Distance on the tree 题目链接 https://nanti.jisuanke.com/t/38229 Describe DSM(Data Structure Master) onc ...
- 南昌网络赛J. Distance on the tree 树链剖分+主席树
Distance on the tree 题目链接 https://nanti.jisuanke.com/t/38229 Describe DSM(Data Structure Master) onc ...
- 2019南昌网络赛 J Distance on the tree 主席树+lca
题意 给一颗树,每条边有边权,每次询问\(u\)到\(v\)的路径中有多少边的边权小于等于\(k\) 分析 在树的每个点上建\(1\)到\(i\)的权值线段树,查询的时候同时跑\(u,v,lca ...
随机推荐
- DataSnap 多层返回数据集分析FireDAC JSON
采用服务器返回数据,一种是返回字符串数据例如JSON,跨平台跨语言,任何语言调用都支持兼容,类似WEBService. 第二种是紧密结合c++builder语言,传输DataSet,可以是Client ...
- 从0开始的Python学习006流程控制
流程控制语句 Python中有三种控制流程语句: if.for.和while. if语句 使用if语句来校验一个条件,如果条件为真(True),运行if-块,如果为假(False),运行else-块. ...
- nginx多server配置记录
直接在配置文件(/etc/nginx/nginx.conf)中添加如下代码: server { listen 8080; server_name 192.168.100.174:8080; root ...
- serversql数据库的查询操作
sql数据库 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !importan ...
- c/c++ 多线程 等待一次性事件 packaged_task用法
多线程 等待一次性事件 packaged_task用法 背景:不是很明白,不知道为了解决什么业务场景,感觉std::asynck可以优雅的搞定一切,一次等待性事件,为什么还有个packaged_tas ...
- c/c++ 多线程 参数传递
多线程 参数传递 1,值传递,拷贝一份新的给新的线程.线程1中有个int变量a,在线程1中启动线程2,参数是a的值,这时就会拷贝a,线程1和线程2不共享a. 2,引用传递,不拷贝一份新的给新的线程.线 ...
- c/c++ 多线程 detach的困惑
多线程 detach的困惑 求大神解答: 1,当在一个函数里启动一个线程后,并detach了 2,detach的线程里使用了这个函数里new出来的一个对象 3,detach后,delete了这个对象 ...
- 6.1Python数据处理篇之pandas学习系列(一)认识pandas
目录 目录 (一)介绍与测试 2.作用: 3.导入的格式 4.小测试 (二)数据类型 1.两种重要的数据类型 2.pandas与numpy的比较 目录 (一)介绍与测试 号称处理数据与分析数据最好的第 ...
- 【字】biang
biang biang面的名字由来:biangbiang面是陕西关中地区的一中地区美食,因为在做这种面时会发出biang biang的声音,biang biang面因此得名.biang字简体共有42笔 ...
- 平滑升级你的Nginx
1.概述(可以直接跳过看第2部分) Nginx方便地帮助我们实现了平滑升级.其原理简单概括,就是: (1)在不停掉老进程的情况下,启动新进程. (2)老进程负责处理仍然没有处理完的请求,但不再接受处理 ...