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 ...
随机推荐
- Python第八天 模块 包 全局变量和内置变量__name__ Python path
Python第八天 模块 包 全局变量和内置变量__name__ Python path 目录 Pycharm使用技巧(转载) Python第一天 安装 shell 文件 Pyt ...
- mybatis配置文件说明(configuration)
1. xml结构(可查看mybatis-3-config.dtd) <!ELEMENT configuration (properties?, settings?, typeAliases?, ...
- duilib
https://www.cnblogs.com/lin1270/p/4109305.html
- BZOJ 3684 大朋友和多叉树
BZOJ 3684 大朋友和多叉树 Description 我们的大朋友很喜欢计算机科学,而且尤其喜欢多叉树.对于一棵带有正整数点权的有根多叉树,如果它满足这样的性质,我们的大朋友就会将其称作神犇的: ...
- day 15 模块、起别名、from导入
模块 '''模块:一系列功能的集合体定义模块:创建一个py文件就是一个模块,该py文件名就是模块名使用模块:在要使用模块的文件中,通过 import 模块名 来导入模块 ''''''import ...
- Thread类的其他方法,同步锁,死锁与递归锁,信号量,事件,条件,定时器,队列,Python标准模块--concurrent.futures
参考博客: https://www.cnblogs.com/xiao987334176/p/9046028.html 线程简述 什么是线程?线程是cpu调度的最小单位进程是资源分配的最小单位 进程和线 ...
- C# — 调用dll出现试图加载不正确格式的程序问题
今天在调用百度dll包时,运行项目出现了如下警告: 修改:鼠标右击项目名称----选择属性----生成-----平台目标-----X64(由于我调用的是X64的dll包,所以这里选择X64,网上许多说 ...
- UI Automator 常用 API 整理
主要类: import android.support.test.uiautomator.UiDevice; 作用:设备封装类,测试过程中获取设备信息和设备交互. import android.sup ...
- HyperLedger Fabric ChainCode开发——shim.ChaincodeStubInterface用法
深蓝前几篇博客讲了Fabric的环境搭建,在环境搭建好后,我们就可以进行Fabric的开发工作了.Fabric的开发主要分成2部分,ChainCode链上代码开发和基于SDK的Application开 ...
- 0.[Andriod]之从零安装配置Android Studio并编写第一个Android App
0. 所需的安装文件 笔者做了几年WP,近来对Android有点兴趣,尝试一下Android开发,废话不多说,直接进入主题,先安装开发环境,笔者的系统环境为windows8.1&x64. 安装 ...