2018.07.18 [NOI2018]归程(return)(kruskal重构树)
传送门
新鲜出炉的noi2018试题。
下面讲讲这题的解法:
首先要学习一个叫做kruskal重构树的东东。
听名字就知道跟kruskal算法有关,没错,原来的kruskal算法就是用并查集实现的,但当我们使用kruskal重构树的时候,对于每次找出的不同的两个连通块的祖先,我们都新建一个点作为两个祖先的父亲,并将当前边的边权转化为新点的点权。然而,路径压缩的时候会让我们丢失这种辛辛苦苦创造的树的形状。。。因此我们需要在使用并查集维护连通性的同时使用二叉树来维护树的形状。这样维护出来的树就是kruskal重构树。
不难发现kruskal重构树有几条重要的性质:
1.树上除叶子结点以外的点都对应着原来生成树中的边,叶子结点就是原来生成树上的节点。
2.由于新点的创建顺序与原来生成树上边权的大小有关,可以发现,从每个点到根节点上除叶子结点外按顺序访问到的点的点权是单调的。
3.出于kruskal算法贪心的性质,两个点u和v的lca的点权就对应着它们最小生成树上的瓶颈。
4.实际上这棵树就是一个二叉堆
所以这道题如何用krukal重构树做呢?
如果我们以海拔为第一关键字对边进行从大到小的排序,然后修建kruskal重构树,这样就弄出了一颗以海拔为关键字的小根堆。然后对于每一棵子树,如果询问中的水位线是低于子树的根节点的,那么此时这棵子树中的所有叶子结点都是连通的。放到题中就是说这颗子树中任选一个点出发,到子树中的其它点都不需要花费。
然后我们假设对于当前询问,我们找到了一个子树的根节点u,满足d[u]>p且d[fa[u]]<=p且出发点v在子树中,这时从v出发可以直接抵达子树中的任意一个叶子结点。因此我们需要从众多叶子节点中选出一个距离1号点花费最小的。
然后再捋一捋思路。我们首先要求出每个点到1号点的最小花费,这个直接dijstra+最短路预处理。然后是要建出kruskal重构树,再然后维护以每个点作为根节点时子树中距离1号点的最小花费,这个建完树后一个简单的dfs搞定。最后是如何找到点u,这时我们要让一个重要的算法登场:倍增算法。直接加上点权>p的限制在树上倍增即可。
总时间复杂度O(T*nlogn)。
代码如下:
#include<bits/stdc++.h>
#define N 400005
#define M 800005
using namespace std;
inline int read(){
int ans=0;
char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
return ans;
}
inline void write(int x){
if(x>9)write(x/10);
putchar(x%10+'0');
}
int n,m,T,q,k,s,vis[N],first[N<<1],head[N],cntx=0,d[N],dep[N],f[N][20],fa[N<<1],lastans=0,totx=0;
struct Node{int u,v,l,a;}e[M],p[N<<1];
struct edge{int v,next;}tr[M<<1];
struct node{int v,next,w;}t[M];
struct heap{int u,v;};
inline bool operator<(heap a,heap b){return a.v>b.v;}
inline void dijstra(int s=1){
memset(vis,false,sizeof(vis));
memset(d,0x3f,sizeof(d));
priority_queue<heap>q;
d[s]=0;
q.push((heap){s,d[s]});
while(!q.empty()){
heap x=q.top();
q.pop();
if(vis[x.u])continue;
vis[x.u]=true;
for(int i=head[x.u];i;i=t[i].next){
int v=t[i].v;
if(vis[v])continue;
if(d[v]>d[x.u]+t[i].w){
d[v]=d[x.u]+t[i].w
;
q.push((heap){v,d[v]});
}
}
}
for(int i=1;i<=n;++i)p[i].l=d[i];
}
inline bool cmp(Node a,Node b){return a.a>b.a;}
inline int find(int x){return x==fa[x]?fa[x]:fa[x]=find(fa[x]);}
inline void add(int u,int v){
tr[++cntx].v=v;
tr[cntx].next=first[u];
first[u]=cntx;
}
inline void addx(int u,int v,int w){
t[++totx].v=v;
t[totx].next=head[u];
t[totx].w=w;
head[u]=totx;
}
inline void dfs(int u,int pa){
dep[u]=dep[pa]+1,f[u][0]=pa;
for(int i=1;i<=19;++i)f[u][i]=f[f[u][i-1]][i-1];
for(int i=first[u];i;i=tr[i].next){
int v=tr[i].v;
dfs(v,u);
p[u].l=min(p[u].l,p[v].l);
}
}
inline int query(int x,int y){
for(int i=19;i>=0;--i)if(dep[x]-(1<<i)>0&&p[f[x][i]].a>y)x=f[x][i];
return p[x].l;
}
inline void kruskal(){
int tot=0,cnt=n;
for(int i=1;i<=(n<<1);++i)fa[i]=i;
sort(e+1,e+m+1,cmp);
for(int i=1;i<=m;++i){
int u=e[i].u,v=e[i].v;
int fx=find(u),fy=find(v);
if(fx!=fy){
add(++cnt,fx);
add(cnt,fy);
fa[fx]=cnt;
fa[fy]=cnt;
p[cnt].a=e[i].a;
++tot;
}
if(tot==n-1)break;
}
dfs(cnt,0);
while(q--){
int x=(k*lastans+read()-1)%n+1,y=(k*lastans+read())%(s+1);
write(lastans=query(x,y));
puts("");
}
}
int main(){
T=read();
while(T--){
lastans=0,n=read(),m=read();
memset(e,0,sizeof(e)),cntx=0,totx=0;
memset(first,0,sizeof(first));
memset(head,0,sizeof(head));
memset(f,0,sizeof(f));
for(int i=1;i<=m;++i)e[i].u=read(),e[i].v=read(),e[i].l=read(),e[i].a=read(),addx(e[i].u,e[i].v,e[i].l),addx(e[i].v,e[i].u,e[i].l);
for(int i=n+1;i<=(n<<1);++i)p[i].l=0x3f3f3f3f;
dijstra();
q=read(),k=read(),s=read();
kruskal();
}
return 0;
}
2018.07.18 [NOI2018]归程(return)(kruskal重构树)的更多相关文章
- [luogu4768] [NOI2018] 归程 (Dijkstra+Kruskal重构树)
[luogu4768] [NOI2018] 归程 (Dijkstra+Kruskal重构树) 题面 题面较长,这里就不贴了 分析 看到不能经过有积水的边,即不能经过边权小于一定值的边,我们想到了kru ...
- Luogu P4768 [NOI2018]归程(Dijkstra+Kruskal重构树)
P4768 [NOI2018]归程 题面 题目描述 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 \(n\) 个节点. \(m\) 条边的无向连通图(节点的编 ...
- P4768 [NOI2018]归程(kruskal 重构树)
洛谷P4768 [NOI2018]归程 LOJ#2718.「NOI2018」归程 用到 kruskal 重构树,所以先说这是个啥 显然,这和 kruskal 算法有关系 (废话 这个重构树是一个有点权 ...
- NOI2018归程(Kruskal重构树)
题目描述 本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定. 魔力之都可以抽象成一个 n 个节点.m 条边的无向连通图(节点的编号从 1 至 n). 我们依次用 l,a 描述一条边的长度. ...
- NOI2018 D1T1 洛谷P4768 归程 (Kruskal重构树)
实际上是一个最短路问题,但加上了海拔这个条件限制,要在海拔<水位线p中找最短路. 这里使用Kruskal重构树,将其按海拔建成小根堆,我们就可以在树中用倍增找出他不得不下车的点:树中节点有两个权 ...
- 【NOI 2018】归程(Kruskal重构树)
题面在这里就不放了. 同步赛在做这个题的时候,心里有点纠结,很容易想到离线的做法,将边和询问一起按水位线排序,模拟水位下降,维护当前的各个联通块中距离$1$最近的距离,每次遇到询问时输出所在联通块的信 ...
- 「NOI 2018」归程「Kruskal 重构树」
题解 Kruskal重构树:每次一条边连接两个集合,建一个新点,点权为该边边权:把这两个集合的根连向新点. 性质:(如果求的是最大生成树)叶子结点是图中实际结点:叶子到根路径上点权递减:两点间lca的 ...
- 【BZOJ5415&UOJ393】归程(Kruskal重构树,最短路)
题意:From https://www.cnblogs.com/Memory-of-winter/p/11628351.html 思路:先从1开始跑一遍dijkstra,建出kruskal重构树之后每 ...
- [NOI2018]归程(kruscal重构树)
[NOI2018]归程 题面太长辣,戳这里 模拟赛上写了一个spfa (关于spfa,它已经死了),然后一个st表水完暴力跑路.考后说是Kruscal重构树或者可持久化并查集???这都是些什么东西.不 ...
随机推荐
- Fiddler 链接到逍遥安卓模拟器
参考: http://www.xyaz.cn/thread-4664-1-1.html 1丶启动Fiddler,打开菜单栏中的 工具 >Fiddler选项 ,打开“Fiddler选项”对话框. ...
- mybatis 常用jdbcType数据类型
来自 : http://blog.csdn.net/paincupid/article/details/50922981
- Mybatis知识(1)
1.#{}和${}的区别是什么? #{}是预编译处理,${}是字符串替换. Mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋值: M ...
- 快快快!27个提升效率的iOS开源库推荐(转)
CSDN移动将持续为您优选移动开发的精华内容,共同探讨移动开发的技术热点话题,涵盖移动应用.开发工具.移动游戏及引擎.智能硬件.物联网等方方面面.如果您想投稿.参与内容翻译工作,或寻求近匠报道,请发送 ...
- Centos 安装golang beego
刚开始用go和beego,真的还好多不懂~希望对看到的朋友有帮助~ 本人环境:centos 6.3x86_64 1.我在机器上创建了一个用户zww(useradd zww),登录zww(su zww) ...
- 尚硅谷STRUTS2视频学习笔记
上一个月一直在学习STRUTS2,学习的是尚硅谷佟刚老师的视频,因为很喜欢佟刚老师的声音,而且他讲的很细,笔记做的也多,基本上是照着他的视频完整的敲了一遍代码,下面就把学习到的知识梳理一遍,最后把项目 ...
- 【340】GIS related knowledge
mpk: 对地图文档以及所有引用的数据源进行打包可创建经过压缩的单个 .mpk 文件.参考,可以通过 Package Map 工具实现. Collector for ArcGIS Survey123 ...
- 专业英语词汇(Java)
abstract (关键字) 抽象 ['.bstr.kt] access vt.访问,存取 ['.kses]‘(n.入口, ...
- 大型运输行业实战_day08_1_memcache缓存生产应用
1.memcache使用环境搭建 1.安装memcached服务器 安装方法 以管理员身份打开cmd,在cmd中执行如下命令: 注意:在执行该命令时必须在memcached.exe文件下执行. 2.开 ...
- https 证书传递、验证和数据加密、解密过程解析
我们都知道HTTPS能够加密信息,以免敏感信息被第三方获取.所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议. HTTPS简介 HTTPS其实是有两部分组成:HTTP + SSL ...