Distance on the tree

DSM(Data Structure Master) once learned about tree when he was preparing for NOIP(National Olympiad in Informatics in Provinces) in Senior High School. So when in Data Structure Class in College, he is always absent-minded about what the teacher says.

The experienced and knowledgeable teacher had known about him even before the first class. However, she didn't wish an informatics genius would destroy himself with idleness. After she knew that he was so interested in ACM(ACM International Collegiate Programming Contest), she finally made a plan to teach him to work hard in class, for knowledge is infinite.

This day, the teacher teaches about trees." A tree with nn nodes, can be defined as a graph with only one connected component and no cycle. So it has exactly n-1n−1 edges..." DSM is nearly asleep until he is questioned by teacher. " I have known you are called Data Structure Master in Graph Theory, so here is a problem. "" A tree with nn nodes, which is numbered from 11to nn. Edge between each two adjacent vertexes uu and vv has a value w, you're asked to answer the number of edge whose value is no more than kk during the path between uu and vv."" If you can't solve the problem during the break, we will call you DaShaMao(Foolish Idiot) later on."

The problem seems quite easy for DSM. However, it can hardly be solved in a break. It's such a disgrace if DSM can't solve the problem. So during the break, he telephones you just for help. Can you save him for his dignity?

Input

In the first line there are two integers n,mn,m, represent the number of vertexes on the tree and queries(2 \le n \le 10^5,1 \le m \le 10^52≤n≤105,1≤m≤105)

The next n-1n−1 lines, each line contains three integers u,v,wu,v,w, indicates there is an undirected edge between nodes uu and vv with value ww. (1 \le u,v \le n,1 \le w \le 10^91≤u,v≤n,1≤w≤109)

The next mm lines, each line contains three integers u,v,ku,v,k , be consistent with the problem given by the teacher above. (1 \le u,v \le n,0 \le k \le 10^9)(1≤u,v≤n,0≤k≤109)

Output

For each query, just print a single line contains the number of edges which meet the condition.

样例输入1复制

3 3
1 3 2
2 3 7
1 3 0
1 2 4
1 2 7

样例输出1复制

0
1
2

样例输入2复制

5 2
1 2 1000000000
1 3 1000000000
2 4 1000000000
3 5 1000000000
2 3 1000000000
4 5 1000000000

样例输出2复制

2
4

题意就是给你一棵树,查询求a到b路径上,边权小于等于k的边有几条。

树链剖分+可持久化线段树。

查询的时候,将树链对应到可持久化线段树上的时候要传到上一个历史版本,所以查询的时候要判断一下。然后就是更新的时候,dfs进行更新,因为tid对应的rt是通过dfs得到的,直接遍历通过dep找爸爸和儿子然后更新是不对的,tid对应的rt是不正确的。其他的代码里写了注释。

两种写法:

1.树链剖分(边权)+可持久化线段树(区间小于等于k的数的个数)+离散化+离线处理

2.树上第k大(主席树)+二分+离散化+在线查询

第二个写法是因为有板子,但是是点权的,直接边权下放到点权,然后二分找k大,然后判断找个数。第二个写法跑的慢,但是能过题就是好代码。

第二个写法模板参考来源:SPOJ COT Count on a tree 树上第k大(主席树)

第一种写法代码:

 //做法一:
//树链剖分(边权)+可持久化线段树
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+;
const int inf=0x3f3f3f3f; #define lson l,m
#define rson m+1,r int sum[maxn<<],b[maxn],value[maxn<<],ls[maxn<<],rs[maxn<<];
int top[maxn],tid[maxn],pos[maxn],fa[maxn],rt[maxn];
int siz[maxn],dep[maxn],son[maxn];
int head[maxn],cnt,num,n,m,d,sz; struct Edge{
int to,next,val;
}edge[maxn<<]; void init()//初始化
{
memset(head,-,sizeof(head));
memset(son,-,sizeof(son));
cnt=num=sz=;
} void add(int u,int v,int w)//存图
{
edge[cnt].to=v;
edge[cnt].val=w;
edge[cnt].next=head[u];
head[u]=cnt++;
} //树链剖分部分
void dfs1(int u,int father)//第一遍dfs,找父节点,深度,重儿子
{
siz[u]=;//当前节点的size
fa[u]=father;//保存父节点
dep[u]=dep[father]+;//记录深度
for(int i=head[u];~i;i=edge[i].next){//遍历
int v=edge[i].to;
int w=edge[i].val;
if(v!=father){//如果连接的是当前节点的父节点,不进行处理,不是就进行处理
value[v]=w;//节点下放,边权下放到节点
dfs1(v,u);
siz[u]+=siz[v];//子树size直接加进来
if(son[u]==-||siz[v]>siz[son[u]])//如果没有设置过重节点son或者子节点v的size大于之前记录的重节点,更新
son[u]=v;//保存重儿子
}
}
} void dfs2(int u,int tp)//第二遍dfs,将各个重节点连接成重链,轻节点连接成轻链,并且将重链(区间)用数据结构去维护,并且为每个节点进行编号,就是dfs的顺序(tid数组),以及当前节点所在的链的起点(top),以及当前节点在树中的位置(pos)
{
top[u]=tp;//保存当前节点所在链的顶端节点,当前节点在的链的起点
tid[u]=++num;//保存树中每个节点剖分之后的新编号,
pos[tid[u]]=value[u];//当前节点的权值在树中的位置,设置新节点为当前节点对应的权值
if(son[u]==-) return ;//如果当前节点不在重链上,不进行处理
dfs2(son[u],tp);//将这条链的所有节点都更新成tp,(就是当前节点所在链的起始节点的重儿子)
for(int i=head[u];~i;i=edge[i].next){//遍历
int v=edge[i].to;
if(v!=son[u]&&v!=fa[u]) dfs2(v,v);//如果当前节点不是所在链的重节点也不是u的父节点,就把top设置成自己,进一步递归
}
} //void build(int &rt,int l,int r)//建一棵空树,也可以不建树,不建树跑的快
//{
// rt=++sz;sum[rt]=0;
// if(l==r){
// return ;
// }
//
// int m=(l+r)>>1;
// build(ls[rt],lson);
// build(rs[rt],rson);
//} void update(int pre,int &rt,int l,int r,int p)//可持久化线段树更新操作
{
rt=++sz;sum[rt]=sum[pre]+;
ls[rt]=ls[pre];rs[rt]=rs[pre];
if(l==r){
return ;
} int m=(l+r)>>;
if(p<=m) update(ls[pre],ls[rt],lson,p);
else update(rs[pre],rs[rt],rson,p);
} int query(int pre,int &rt,int L,int R,int l,int r)//查找就可以了
{
if(L>R) return ;
if(L<=l&&r<=R){
return sum[rt]-sum[pre];
} int m=(l+r)>>;
int ret=;
if(L<=m) ret+=query(ls[pre],ls[rt],L,R,lson);
if(R> m) ret+=query(rs[pre],rs[rt],L,R,rson);
return ret;
} //当时就是查询写捞了。。。
int getnum(int u,int v,int l,int r)//最重要的操作,将剖分下来的链与数据结构联系起来,lca的操作通过top往上跳实现,top相同的话就是lca的节点
{
int ans=;
while(top[u]!=top[v]){//如果两点的top节点不同,所在链的起始点相同就表示找到了lca
if(dep[top[u]]<dep[top[v]]) swap(u,v);//始终让top[u]的深度大于top[v]的,查询深度大的
// ans+=query(rt[tid[top[u]]],rt[tid[u]],l,r,1,d);
if(top[u]==) ans+=query(rt[tid[top[u]]],rt[tid[u]],l,r,,d);//查询对应数据结构上的部分,找到对应的左右区间
else ans+=query(rt[tid[fa[top[u]]]],rt[tid[u]],l,r,,d);
u=fa[top[u]];//将其修改为起始节点的父节点,走轻边,继续循环
} if(dep[v]<dep[u]) swap(u,v);//如果查询的是在一个完整的区间的一部分,直接查询就可以
// ans+=query(rt[tid[son[u]]],rt[tid[v]],l,r,1,d);
ans+=query(rt[tid[u]],rt[tid[v]],l,r,,d);//因为是可持久化线段树,虽然是查询tid[son[u]],tid[v],但是要左边再往上一个版本,就是son[u]的爸爸,就是u自己,所以是rt[tid[u]]],rt[tid[v]],
return ans;
} void dfs(int u,int father)//将剖下来的链对应到可持久化线段树上
{
update(rt[tid[father]],rt[tid[u]],,d,value[u]);
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].to;
if(v!=father){
dfs(v,u);
}
}
} struct Eg{
int u,v,val;
}eg[maxn]; struct Ask{
int l,r,k;
}ask[maxn]; int main()
{
scanf("%d%d",&n,&m);
init();
for(int i=;i<n;i++){
int u,v,w;
scanf("%d%d%d",&eg[i].u,&eg[i].v,&eg[i].val);
b[i]=eg[i].val;
}
for(int i=;i<=m;i++){
scanf("%d%d%d",&ask[i].l,&ask[i].r,&ask[i].k);
b[i+n-]=ask[i].k;
}
sort(b+,b+n+m);
d=unique(b+,b+n+m)-b;
for(int i=;i<n;i++){
eg[i].val=lower_bound(b+,b++d,eg[i].val)-b;
add(eg[i].u,eg[i].v,eg[i].val);
add(eg[i].v,eg[i].u,eg[i].val);
}
dfs1(,);
dfs2(,);
// build(rt[0],1,d);
dfs(,);
// for(int i=1;i<=n;i++)
// cout<<rt[i]<<endl;
// for(int i=1;i<n;i++){//不能遍历更新,如果不是可持久化线段树可以遍历更新,可持久化线段树更新和上一个历史版本有关,直接更新,tid对应的历史版本不一定对,只能dfs的时候进行更新,历史版本和在dfs上更新是一样的,因为历史版本就是通过dfs的时候求出来的。历史版本和tid才能对应上。
// if(dep[eg[i].u]>dep[eg[i].v]) swap(eg[i].u,eg[i].v);
//// cout<<rt[tid[eg[i].u]]<<" "<<rt[tid[eg[i].v]]<<" "<<eg[i].val<<endl;
// update(rt[tid[eg[i].u]],rt[tid[eg[i].v]],1,d,eg[i].val);
// }
// for(int i=1;i<=n;i++){
// cout<<rt[i]<<endl;
// }
for(int i=;i<=m;i++){
ask[i].k=lower_bound(b+,b++d,ask[i].k)-b;
int ans=getnum(ask[i].l,ask[i].r,,ask[i].k);
printf("%d\n",ans);
}
} /*
4 2
1 2 1
2 4 2
1 3 3
1 4 2
3 4 3 2
3
*/

第二种写法代码:

 //做法二:
//树链剖分+主席树-变权下放,二分找K大找数
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+;
const int inf=0x3f3f3f3f; #define lson l,m
#define rson m+1,r struct edge{
int to,next,val;
}g[maxn<<]; int n,m,sz;
int cnt,head[maxn];
int len,root[maxn],ls[maxn*],rs[maxn*],val[maxn*];
int num,dep[maxn<<],ver[maxn<<],fir[maxn],dp[][maxn<<],fa[maxn];
bool vis[maxn];
int a[maxn],b[maxn]; void init()
{
memset(vis,false,sizeof vis);
memset(head,-,sizeof head);
cnt=num=sz=;
} void add(int u,int v,int w)
{
g[cnt].to=v;
g[cnt].val=w;
g[cnt].next=head[u];
head[u]=cnt++;
} //void build(int &rt,int l,int r)
//{
// rt=++sz;val[rt]=0;
// if(l==r){
// return ;
// }
//
// int m=(l+r)>>1;
// build(ls[rt],lson);
// build(rs[rt],rson);
//} void update(int pre,int &rt,int l,int r,int p)
{
rt=++sz;val[rt]=val[pre]+;
ls[rt]=ls[pre];rs[rt]=rs[pre];
if(l==r){
return ;
} int m=(l+r)>>;
if(p<=m) update(ls[pre],ls[rt],lson,p);
else update(rs[pre],rs[rt],rson,p);
} void dfs(int u,int father,int d)
{
vis[u]=true;
fa[u]=father;
ver[++num]=u;
dep[num]=d;
fir[u]=num;//dfs序的编号
update(root[father],root[u],,len,a[u]);//在父节点的基础上新建一棵树
for(int i=head[u];i!=-;i=g[i].next){
int v=g[i].to;
if(!vis[v]){
dfs(v,u,d+);
ver[++num]=u;
dep[num]=d;
}
}
} void ST(int n)
{
for(int i=;i<=n;i++){
dp[][i]=i;
}
for(int i=;(<<i)<=n;i++){
for(int j=;j<=n-(<<i)+;j++){
int a=dp[i-][j],b=dp[i-][j+(<<(i-))];
dp[i][j]=dep[a]<dep[b]? a:b;
}
}
} int RMQ(int l,int r)
{
int k=log(r-l+)/log();
int a=dp[k][l],b=dp[k][r-(<<k)+];
return dep[a]<dep[b]? a:b;
} int LCA(int u,int v)
{
u=fir[u];v=fir[v];
if(u>v) swap(u,v);
int res=RMQ(u,v);
return ver[res];
} int query(int ss, int tt, int lca, int lca_fa, int l, int r, int k)
{
if(l == r){
return l;
} int m=(l+r)>>;
int tmp=val[ls[ss]]+val[ls[tt]]-val[ls[lca]]-val[ls[lca_fa]];
if(k<=tmp) return query(ls[ss], ls[tt], ls[lca], ls[lca_fa],lson, k);
else return query(rs[ss], rs[tt], rs[lca], rs[lca_fa], rson, k - tmp);
} void dfss(int u,int father)
{
for(int i=head[u];i!=-;i=g[i].next){
int v=g[i].to;
int w=g[i].val;
if(v!=father){
a[v]=w;
dfss(v,u);
}
}
} int main()
{
scanf("%d%d",&n,&m);
init();
for(int i=;i<n;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
add(v,u,w);
}
dfss(,);
a[]=inf;
for(int i=;i<=n;i++){
b[i]=a[i];
}
sort(b+,b+n+);
len=unique(b+,b+n+)-b-;
for(int i=;i<=n;i++){
a[i]=lower_bound(b+,b++len,a[i])-b;
}
for(int i=len+;i<=1e5+;i++){
b[i]=inf;
}
// build(root[0],1,len);
dfs(,,);
ST(*n-);
for(int i=;i<=m;i++){
int u,v,k;
scanf("%d%d%d",&u,&v,&k);
int lca=LCA(u,v);
int l=,r=1e5+,ans=;
while(l<=r){
int mid=(l+r)/;
if(k>=b[query(root[u],root[v],root[lca],root[fa[lca]],,len,mid)])
l=mid+,ans=mid;
else
r=mid-;
}
if(b[a[lca]]<=k) ans--;
printf("%d\n",ans);
}
return ;
} /*
7 1
1 2 10
1 3 8
3 4 9
3 5 20
4 6 100
4 7 80
6 7 30
*/

好菜啊,自闭,第二种写法队友过的,比赛时改不来板子,队友直接按点权的写的,然后二分判断的,Orz。

第一种写法赛后补题过的,查询函数对应查询范围写捞了,自闭。。。

菜到变形,难受。

计蒜客 38229.Distance on the tree-1.树链剖分(边权)+可持久化线段树(区间小于等于k的数的个数)+离散化+离线处理 or 2.树上第k大(主席树)+二分+离散化+在线查询 (The Preliminary Contest for ICPC China Nanchang National Invitational 南昌邀请赛网络赛)的更多相关文章

  1. 计蒜客 38228. Max answer-线段树维护单调栈(The Preliminary Contest for ICPC China Nanchang National Invitational I. Max answer 南昌邀请赛网络赛) 2019ICPC南昌邀请赛网络赛

    Max answer Alice has a magic array. She suggests that the value of a interval is equal to the sum of ...

  2. The Preliminary Contest for ICPC China Nanchang National Invitational I. Max answer (单调栈+线段树)

    题目链接:https://nanti.jisuanke.com/t/38228 题目大意:一个区间的值等于该区间的和乘以区间的最小值.给出一个含有n个数的序列(序列的值有正有负),找到该序列的区间最大 ...

  3. 2019 The Preliminary Contest for ICPC China Nanchang National Invitational(A 、H 、I 、K 、M)

    A. PERFECT NUMBER PROBLEM 题目链接:https://nanti.jisuanke.com/t/38220 题意: 输出前五个完美数 分析: 签到.直接百度完美数输出即可 #i ...

  4. The 2019 ICPC China Nanchang National Invitational and International Silk-Road Programming Contest - F.Sequence(打表+线段树)

    题意:给你一个长度为$n$的数组,定义函数$f(l,r)=a_{l} \oplus a_{l+1} \oplus...\oplus a_{r}$,$F(l,r)=f(l,l)\oplus f(l,l+ ...

  5. POJ3237 Tree 树链剖分 边权

    POJ3237 Tree 树链剖分 边权 传送门:http://poj.org/problem?id=3237 题意: n个点的,n-1条边 修改单边边权 将a->b的边权取反 查询a-> ...

  6. HDU 4417.Super Mario-可持久化线段树(无修改区间小于等于H的数的个数)

    Super Mario Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  7. POJ2763 Housewife Wind 树链剖分 边权

    POJ2763 Housewife Wind 树链剖分 边权 传送门:http://poj.org/problem?id=2763 题意: n个点的,n-1条边,有边权 修改单边边权 询问 输出 当前 ...

  8. HDU3669 Aragorn's Story 树链剖分 点权

    HDU3669 Aragorn's Story 树链剖分 点权 传送门:http://acm.hdu.edu.cn/showproblem.php?pid=3966 题意: n个点的,m条边,每个点都 ...

  9. BZOJ 1036 [ZJOI2008]树的统计Count (树链剖分 - 点权剖分 - 单点权修改)

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1036 树链剖分模版题,打的时候注意点就行.做这题的时候,真的傻了,单词拼错检查了一个多小时 ...

随机推荐

  1. Tomcat设置编码问题

    为了解决编码问题,在tomcat的server.xml文件中添加了useBodyEncodingForURI="true"配置,如下 <Connector port=&quo ...

  2. asp.net WebForm程序删除.designer.cs文件之后的故事

    1.介绍 正常情况下添加一个WebForm程序结构如下(命名为:myWebForm.aspx) 文件说明:.aspx文件:书写html代码部分,以及javascript,css等代码书写及引用 .as ...

  3. 如何使用Defender优雅的管理权限?

    何为权限管理 权限管理已经不知不觉深入到了我们生活的每一个角落,例如地铁进站的闸机,高速公路上的过路费,停车场的杠杆等等等等. 作为一名开发人员,权限二字对我们的映像更加深刻,无论任何系统,都多多少少 ...

  4. NYOJ 119 士兵杀敌(三) (线段树)

    题目链接 描述 南将军统率着N个士兵,士兵分别编号为1~N,南将军经常爱拿某一段编号内杀敌数最高的人与杀敌数最低的人进行比较,计算出两个人的杀敌数差值,用这种方法一方面能鼓舞杀敌数高的人,另一方面也算 ...

  5. Python练习-一个简单的生成器

    今天我们学习了生成器,怎么理解生成器呢,其实就是使用函数的方式自己建立一个迭代器 # 编辑者:闫龙 #做一个简单的生成器 def EasyGene(*args): #建立一个生成器方法并传递多个参数 ...

  6. Linux基础-rpm软件包管理

    任务:挂载光盘文件到/media目录,进去/media目录下的Packages目录,查看系统已安装的所有rpm包,查看系统是否安装dhcp软件包,安装dhcp软件包,查看dhcp软件包的信息,查看dh ...

  7. 20165230 2017-2018-2 《Java程序设计》第8周学习总结

    20165230 2017-2018-2 <Java程序设计>第8周学习总结 教材学习内容总结 第十二章 java多线程机制 一个进程在其执行过程中,可产生多个线程.线程是比进程更小的执行 ...

  8. 【比赛游记】THUWC2019酱油记

    往期回顾:THUSC2018酱油记 day 0 早上 7 点的动车,不知道是从哪儿到哪儿的(雾),只知道从福建到广东 233333 一个值得思考的问题:福建人会不会被广东人吃啊? 动车上玩空洞骑士,可 ...

  9. Linux input子系统学习总结(三)----Input设备驱动

    Input 设备驱动 ---操作硬件获取硬件寄存器中设备输入的数据,并把数据交给核心层: 一 .设备驱动的注册步骤: 1.分配一个struct  input_dev :          struct ...

  10. 002_IO磁盘深入理解

    一.如何测试云硬盘 https://www.ustack.com/blog/how-benchmark-ebs/#fio