题目链接:

https://www.luogu.org/problemnew/show/P4197

题目:

在Bytemountains有N座山峰,每座山峰有他的高度$h_i$。有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走

现在有Q组询问,每组询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。

在线做法题解:

一句话题解:kruskal重构树dfs序上建主席树直接查询第k大即可

知识点拓展:

下面讲讲kruskal重构树是干啥的?这是我第一次做kruskal重构树的题目,下面就当是学习笔记了

从这道题来看,显然我们只需要考虑最小生成树上的路径即可

那么所谓kruskal重构树,就是在kruskal的算法过程中搞一波事情。思想就是在建最小生成树的时候不是直接连边,而是新建一个节点,并把这个节点的值设为边权,然后令两个连通块的代表点分别作为它的左右儿子。然后令这个新节点成为整个连通块的代表点

显然这棵树会具备这样的一个性质:从一个节点向他的儿子一路dfs下去,遍历到的点的点权都是单调递减的,因为我们是排序之后不断加边的(父亲肯定比儿子后建立)

那么我们发现原来的每个节点其实就是这棵子树中的叶子节点,要查询节点x出发经过边权不大于val的路径的点,我们就从节点x不断向上直到找到一个最远的祖先的点权刚好小于等于val,这个祖先的子树中的点显然都满足到x的路径上的边都不超过val

怎么找呢?我们可以倍增

这道题中由于我们要查询第k大,也就是我们要对每个节点的子树中的叶子节点维护第k大。显然可以直接转化为dfs序上查询区间第k大,因此我们还需要主席树

据某大佬说,kruskal重构树可以用来解决一系列“查询从某个点出发经过边权不超过val的边所能到达的节点”的问题,可以和其他数据结构(比如主席树)套用来维护更加复杂的询问

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
using namespace std; const int N=1e5+;
const int M=5e5+;
const int NN=N<<;
const int inf=1e9+;
int n,m,q,xys,tim,tot,cnt;
int height[N];
int fa[NN][],value[NN],in[NN],st[NN],ed[NN],head[NN],leaf[NN],pos[NN],root[NN],f[NN];
int lx[NN<<],rx[NN<<],siz[NN<<];
struct E
{
int x,y,w;
}e[M];
struct EDGE
{
int to,nxt;
}edge[NN];
inline int read()
{
char ch=getchar();
int s=,f=;
while (ch<''||ch>'') {if (ch=='-') f=-;ch=getchar();}
while (ch>=''&&ch<='') {s=(s<<)+(s<<)+ch-'';ch=getchar();}
return s*f;
}
bool cmp(E a,E b) {return a.w<b.w;}
void add(int u,int v)
{
edge[++tot]=(EDGE){v,head[u]};
head[u]=tot;
}
int find(int x)
{
if (f[x]!=x) f[x]=find(f[x]);
return f[x];
}
void dfs(int x)
{
pos[++tim]=x;st[x]=tim;
for (int i=;(<<i)<=(n<<);i++) fa[x][i]=fa[fa[x][i-]][i-];
for (int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if (y==fa[x][]) continue;
fa[y][]=x;
dfs(y);
leaf[x]+=leaf[y];
}
if (!leaf[x]) leaf[x]=;
ed[x]=tim;
}
int update(int pre,int l,int r,int x)
{
int o=++cnt;
lx[o]=lx[pre];rx[o]=rx[pre];siz[o]=siz[pre]+;
if (l==r) return o;
int mid=l+r>>;
if (x<=mid) lx[o]=update(lx[pre],l,mid,x);
else rx[o]=update(rx[pre],mid+,r,x);
return o;
}
int kth(int lr,int rr,int l,int r,int k)
{
if (l==r) return l;
int x=siz[rx[rr]]-siz[rx[lr]];
int mid=l+r>>;
if (k<=x) return kth(rx[lr],rx[rr],mid+,r,k);
else return kth(lx[lr],lx[rr],l,mid,k-x);
}
int query(int v,int x,int k)
{
for (int i=;i>=;i--) if (value[fa[v][i]]<=x&&fa[v][i]) v=fa[v][i];
if (leaf[v]<k) return -;
int l=st[v],r=ed[v];
return kth(root[l-],root[r],,inf,k);
}
int main()
{
n=read();m=read();q=read();
for (int i=;i<=n;i++) height[i]=read();
for (int i=;i<=m;i++)
{
e[i].x=read();e[i].y=read();e[i].w=read();
}
sort(e+,e++m,cmp);
xys=n;//原来节点的标号从1-n
for (int i=;i<=n<<;i++) f[i]=i;
for (int i=;i<=m;i++)
{
int fx=find(e[i].x),fy=find(e[i].y);
if (fx!=fy)
{
value[++xys]=e[i].w;
f[fx]=f[fy]=xys;
add(xys,fx);add(xys,fy);in[fx]++;in[fy]++;
}
}
for (int i=;i<=xys;i++) if (!in[i]) dfs(i);
for (int i=;i<=xys;i++)
{
if (pos[i]<=n) root[i]=update(root[i-],,inf,height[pos[i]]);
else root[i]=root[i-];
}
while (q--)
{
int v=read(),x=read(),k=read();
printf("%d\n",query(v,x,k);
}
return ;
}

离线做法题解:

同样我们要在kruskal的算法流程上做文章

考虑把询问按x排序,每次把不大于这个长度的边加入最小生成树,然后对每个联通块建主席树,加边合并主席树。最后再当前查询点所在的主席树中查询第k大即可

这个好想一点

#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
#define mid ((l+r)>>1)
using namespace std; const int M=5e5+;
const int N=1e5+;
const int inf=1e9;
int n,m,Q,tot;
int rt[N],fa[N],lx[N<<],rx[N<<],siz[N<<],ans[M],hei[N];
struct EDGE
{
int x,y,w;
}e[M];
struct Que
{
int id;
int v,x,k;
}q[M];
bool operator < (EDGE x,EDGE y) {return x.w<y.w;}
bool operator < (Que x,Que y) {return x.x<y.x;}
inline int read()
{
char ch=getchar();
int s=,f=;
while (ch<''|ch>'') {if (ch=='-') f=-;ch=getchar();}
while (ch>=''&&ch<='') {s=(s<<)+(s<<)+ch-'';ch=getchar();}
return s*f;
}
int find(int x)
{
if (fa[x]!=x) fa[x]=find(fa[x]);
return fa[x];
}
void update(int &o,int l,int r,int x)
{
if (!o) o=++tot;
siz[o]++;
if (l==r) return;
if (x<=mid) update(lx[o],l,mid,x);
else update(rx[o],mid+,r,x);
}
void merge(int &x,int y)
{
if (!x||!y) {x=x|y;return;}
siz[x]+=siz[y];
merge(lx[x],lx[y]);
merge(rx[x],rx[y]);
}
int kth(int o,int l,int r,int k)
{
if (l==r) return l;
if (siz[rx[o]]>=k) return kth(rx[o],mid+,r,k);
else return kth(lx[o],l,mid,k-siz[rx[o]]);
}
int main()
{
n=read();m=read();Q=read();
for (int i=;i<=n;i++) fa[i]=i,hei[i]=,update(rt[i],,inf,read());
for (int i=;i<=m;i++)
{
e[i].x=read();e[i].y=read();e[i].w=read();
}
sort(e+,e++m);
for (int i=;i<=Q;i++)
{
q[i].id=i;
q[i].v=read();q[i].x=read();q[i].k=read();
}
sort(q+,q++Q);
int l=,cnt=;
for (int i=;i<=Q;i++)
{
int v=q[i].v,x=q[i].x,k=q[i].k;
while (e[l].w<=x&&l<=m)
{
if (cnt==n-) break;
int f1=find(e[l].x),f2=find(e[l].y);
++l;
if (f1==f2) continue;
++cnt;
if (hei[f1]>hei[f2])//启发式合并
{
merge(rt[f1],rt[f2]);
fa[f2]=f1;
}
else if (hei[f1]<hei[f2])
{
merge(rt[f2],rt[f1]);
fa[f1]=f2;
}
else
{
merge(rt[f1],rt[f2]);
fa[f2]=f1;hei[f1]++;
}
}
v=find(v);
if (siz[rt[v]]<k) ans[q[i].id]=-;else ans[q[i].id]=kth(rt[v],,inf,k);
}
for (int i=;i<=Q;i++) printf("%d\n",ans[i]);
return ;
}

[luogu P4197] Peaks 解题报告(在线:kruskal重构树+主席树 离线:主席树+线段树合并)的更多相关文章

  1. [jzoj 5926] [NOIP2018模拟10.25] naive 的图 解题报告(kruskal重构树+二维数点)

    题目链接: https://jzoj.net/senior/#main/show/5926 题目: 题解: 显然最小的最大路径在最小生成树上(最小生成树=最小瓶颈生成树) 于是我们建出kruskal重 ...

  2. BZOJ 3545 / 洛谷 P4197 Peaks 解题报告

    P4197 Peaks 题目描述 在\(\text{Bytemountains}\)有\(N\)座山峰,每座山峰有他的高度\(h_i\).有些山峰之间有双向道路相连,共\(M\)条路径,每条路径有一个 ...

  3. Kruskal 重构树小记

    其实也不是多难的知识点吧--学了一个中午+半个下午就把它学会了(做过那道 jxd 作业 CF571D 的应该比较好理解) Kruskal 重构树大概就是在正常 Kruskal 的时候,对于两个需要连边 ...

  4. NOI2018归程(Kruskal重构树)

    题目描述 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 n 个节点.m 条边的无向连通图(节点的编号从 1 至 n). 我们依次用 l,a 描述一条边的长度. ...

  5. bzoj 3551 kruskal重构树dfs序上的主席树

    强制在线 kruskal重构树,每两点间的最大边权即为其lca的点权. 倍增找,dfs序对应区间搞主席树 #include<cstdio> #include<cstring> ...

  6. [洛谷P4768] [NOI2018]归程 (kruskal重构树模板讲解)

    洛谷题目链接:[NOI2018]归程 因为题面复制过来有点炸格式,所以要看题目就点一下链接吧\(qwq\) 题意: 在一张无向图上,每一条边都有一个长度和海拔高度,小\(Y\)的家在\(1\)节点,并 ...

  7. 水壶-[Kruskal重构树] [解题报告]

    水壶 本来从不写针对某题的题解,但因为自己实在是太蠢了,这道题也神TM的恶心,于是就写篇博客纪念一下 H水壶 时间限制 : 50000 MS 空间限制 : 565536 KB 评测说明 : 2s,51 ...

  8. 洛谷P4197 Peaks(Kruskal重构树 主席树)

    题意 题目链接 往后中文题就不翻译了qwq Sol 又是码农题..出题人这是强行把Kruskal重构树和主席树拼一块了啊.. 首先由于给出的限制条件是<=x,因此我们在最小生成树上走一定是最优的 ...

  9. 洛谷P4197 Peaks (Kruskal重构树)

    读题,只经过困难值小于等于x的路径,容易想到用Kruskal重构树:又要查询第k高的山峰,我们选择用主席树求解. 先做一棵重构树,跑一遍dfs,重构树中每一个非叶子节点对应一段区间,我们开range[ ...

随机推荐

  1. (转)IOS http请求的get 和 post的请求的区别

    从表面的意思看get 和 post的区别get就是获取数据,post就是发送数据.这个是误区.其实两者都可以的,在IOS向服务器发送请求里面可以带参数. 那么这些误区是怎么出现的呢?先看看一下对htt ...

  2. crawler4j详细配置

    控制器类必须传一个类型为CrawlConfig的参数,用于配置crawler4j.下面描述了一些关于配置的细节. 抓取深度 默认情况下没有抓取深度的限制.可以通过配置来限制深度,比如,你有个种子页面A ...

  3. mysql授予IP远程访问访问语句

    mysql -u root -p; GRANT ALL PRIVILEGES ON *.* TO '用户名'@'你的IP地址' IDENTIFIED BY '密码' WITH GRANT OPTION ...

  4. 微信小程序面试题

    小程序与原生App哪个好? 答: 小程序除了拥有公众号的低开发成本.低获客成本低以及无需下载等优势,在服务请求延时与用户使用体验是都得到了较大幅度  的提升,使得其能够承载跟复杂的服务功能以及使用户获 ...

  5. VC工程里,如何编译汇编语言的文件

    最近老是有朋友问,VC工程里,如何编译汇编语言的文件,接下来就说一下步骤: 1.将汇编语言文件,加入到工程里: 2.在Solution Explorer窗口中右键单击Visual C++项目,选择Bu ...

  6. Unity的SendMessage方法

    用法(该对象所有脚本都能收到): gameObject.SendMessage("要执行的方法名"); 通知的另一种实现: gameObject.GetComponent<脚 ...

  7. nginx上搭建https

    nginx上配置https的条件: 1.SSL证书和服务器私钥文件 2.nginx支持SSL模块 一.获取SSL证书 网上有提供权威认证的SSL证书的网站,但多数是收费的,而且不便宜.在正式的生产环境 ...

  8. 使用短信猫发送短信java代码

    短信猫简单配置:https://www.cnblogs.com/Big-Boss/p/9699880.html 发送短信: package utils; import org.smslib.AGate ...

  9. 基础——(5)D Flip-Flop(D触发器)

    之前搞了一个 D-Latch,看一下下图是怎么变化的 In D-latch anytime its enabled the input D is going to be output at Q 使用c ...

  10. Vue学习之路第四篇:v-html指令

    上一篇我们讲解了两种方式,把Vue对象的数据展示在页面上: 1.插值表达式 2.v-text指令 但是如果我们展示的数据包含元素标签或者样式,我们想展示标签或样式所定义的属性作用,该怎么进行渲染,比如 ...