博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
『Kruscal重构树 Exkruscal』
阅读量:7066 次
发布时间:2019-06-28

本文共 4351 字,大约阅读时间需要 14 分钟。

新增一道例题及讲解


Exkruscal

\(Exkruscal\)又称\(Kruscal\)重构树,是一种利用经典算法\(Kruscal\)来实现的构造算法,可以将一张无向图重构为一棵具有\(2n-1\)个节点的树,这棵树具有许多特殊的性质,可以用来解决许多问题。

那么我们来了解一下这个新算法。

Kruscal

先回顾一下\(Kruscal\)算法吧。这是一个经典的图论算法,用于求解无向图的最小生成树。

其算法大致思路为将边用边表的形式储存,按照权值从小到大排序,然后枚举每一条边,尝试连接不在同一联通块中的两个节点,这一过程用并查集来维护。

重构树

那么我们怎么用\(Kruscal\)算法来重构树呢?我们还是先将边按边权从小到大排序,然后按照\(Kruscal\)算法的流程来,每一次加入一条连接两个不同联通块的点,并将这一过程用并查集来维护。

不同的是,对于每一条\(Kruscal\)算法选中的边,我们要新建一个节点,将该点的点权赋值为选中边的边权,并将该点连向选中边连接的两个点。

这样,对于\(Kruscal\)算法选中的\(n-1\)条边,我们就构造出了一个具有\(2n-1\)个节点的树,其中,树的\(n\)个叶节点对应原图的\(n\)个节点它们没有点权,而其他的所有点均有一个点权。

性质

我们已经了解如何用\(Kruscal\)算法重构树了,那么,这棵树具有如下的性质,可以帮助我们解题。

\(1.\)这棵树具有堆性质(大根堆)

\(2.\)原图两点之间路径的最大边权最小值是重构树上两点\(lca\)的权值
\(3.\)树中代表原图中的点的节点全是叶子节点,其余节点都代表了一条边的边权

\(Code:\)

inline void Exkruscal(void){    sort(l+1,l+m+1,compare);//l是边表    for(int i=1;i<=2*n;i++)        fa[i]=i;//并查集初始化    for(int i=1;i<=m;i++)    {        int x=find(l[i].x),y=find(l[i].y);        if(x==y)continue;        fa[x]=fa[y]=++n;        val[n]=l[i].val;//点权        insert(n,x);//连边        insert(n,y);    } }

Network(BZOJ3732)

Description

给你N个点的无向图 (1 <= N <= 15,000),记为:1…N。

图中有M条边 (1 <= M <= 30,000) ,第j条边的长度为: d_j ( 1 < = d_j < = 1,000,000,000).

现在有 K个询问 (1 < = K < = 15,000)。 每个询问的格式是:A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?

Input Format

第一行: N, M, K。

第2..M+1行: 三个正整数:X, Y, and D (1 <= X <=N; 1 <= Y <= N). 表示X与Y之间有一条长度为D的边。
第M+2..M+K+1行: 每行两个整数A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?

Output Format

对每个询问,输出最长的边最小值是多少。

Sample Input

6 6 8  1 2 5  2 3 4  3 4 3  1 4 8  2 5 7  4 6 2  1 2  1 3  1 4  2 3  2 4  5 1  6 2  6 1

Sample Output

5  5  5  4  4  7  4  5

解析

\(Solution\ 1\)

由于最小生成树一定是瓶颈生成树,所以我们可以将原图求最小生成树直接重新建图,保证两点之间路径的最大权值最小。然后树上倍增\(LCA\)顺便求最大值即可。

\(Solution\ 2\)

\(Exkruscal\)模板题,直接\(Kruscal\)重构树,然后求\(LCA\)点权即可。

\(Code:\)

#include
using namespace std;#define mset(name,val) memset(name,val,sizeof name)#define filein(str) freopen(str".in","r",stdin)#define fileout(str) freopen(str".out","w",stdout)const int N=15020,M=30020,K=20020,MaxlogN=25;struct Link{int x,y,val;}l[M];struct edge{int ver,next;}e[2*N];int n,m,q,t,fa[2*N],val[2*N],depth[2*N],f[2*N][MaxlogN],Last[2*N];inline void input(void){ scanf("%d%d%d",&n,&m,&q); for(int i=1;i<=m;i++) scanf("%d%d%d",&l[i].x,&l[i].y,&l[i].val);}inline bool compare(Link a,Link b){ return a.val
depth[y]) swap(x,y); for(int d=depth[y]-depth[x],i=0;d;d>>=1,i++) if(1&d)y=f[y][i]; if(x==y)return x; for(int i=MaxlogN-1;i>=0;i--) { if(f[x][i]^f[y][i]) { x=f[x][i]; y=f[y][i]; } } return f[x][0];}inline void solve(void){ for(int i=1;i<=q;i++) { int a,b; scanf("%d%d",&a,&b); printf("%d\n",val[LCA(a,b)]); }}int main(void){ input(); Exkruscal(); dfs(n,1); dp(); solve(); return 0;}

路径权值

Description

给定一个带权树,树上任意两点间的路径权值d(x,y)定义为x,y这两个点之间路径上的最小值,树上任意一点x的权值定义为这个点到树上其他所有点的路径权值和,即∑d(x,i),现求树上每一个点的路径权值和。

Input Format

首先输入一个整数 n(1≤n≤100000),表示树的点的个数。 

接下来n−1行,每行三个整数 x,y,s(1≤x,y≤n,1≤s≤1000),表示编号为x的节点和编号为y的节点之间存在一条权值为s的边,树上每个点的编号为1~n

Output Format

n行,每行输出每个节点对应的路径权值和

Sample Input

4  1 2 2  2 4 1  2 3 1

Sample Output

4433

解析

看到两点路径间的最小值就想到是可以用\(Exkruscal\)的。

先将边按边权从大到小排序,重构树一下,两点\(LCA\)的权值就是树上两点路径的最小值。对于重构树中的每一个非叶子节点\(x\),它的左子树中的每一个叶节点到右子树中的每一个叶节点的真实路径中必然经过\(x\)所代表的边。

我们设\(p_i\)代表原树中节点\(i\)的路径权值和,那么对于刚才提到的一个非叶子节点\(x\)\(x\)的左子树中所有叶子结点的\(p\)值都应该加上\(x\)右子树大小乘\(val_x\),右子树也是同理(加上\(x\)左子树大小乘\(val_x\))。

最后,我们要输出每个节点的\(p\)值。这样的话,问题就转换成了我们需要一个数据结构,能够实现区间加法和单点查询,可以用树状数组配合差分实现。

还有一个问题,如果做区间加法时,子树中叶节点所对应的编号不连续怎么办,我们可以前序遍历给每一个叶子节点重新标一个编号,用于树状数组,最后输出时输出每个节点对应编号的值即可。

\(Code:\)

#include
using namespace std;const int N=100000+20;int dif[N],n,n_,Last[N*2],t,f[N*2],val[N*2],size[N*2],cnt;struct LINK{int x,y,val;}l[N];struct EDGE{int ver,next;}e[N*2];struct TREE{int l,r;}tree[N*2];inline bool compare(LINK a,LINK b){ return a.val>b.val;}inline int find(int x){ return f[x]==x?x:f[x]=find(f[x]);}inline void insert(int x,int y){ e[++t].ver=y;e[t].next=Last[x];Last[x]=t;}inline int lowbit(int x){return x&(-x);}inline void modify(int pos,int val){ for(;pos<=n_;pos+=lowbit(pos))dif[pos]+=val;} inline int query(int pos){ int res=0; for(;pos;pos-=lowbit(pos))res+=dif[pos]; return res;}inline void input(void){ scanf("%d",&n); for(int i=1;i


转载于:https://www.cnblogs.com/Parsnip/p/10593313.html

你可能感兴趣的文章
MySQL 运维笔记(一)—— 终止高负载SQL
查看>>
Carrie Higbie:数据中心的绿色布线之道
查看>>
ECS之初体验
查看>>
我的友情链接
查看>>
【风云原创】Flash技术将被Html5枪毙,Silverlight将何去何从?
查看>>
power shell测试wmi
查看>>
话里话外:成功CEO的用人之道——按需激励
查看>>
openwrt无线连接互联网的实现原理【1】
查看>>
WPS for Linux(ubuntu)字体配置(字体缺失解决办法)
查看>>
谷歌为Pwnium***竞赛再掷重金 将提供200万美元奖金
查看>>
搭建K8S高可用集群(二进制方式)
查看>>
BSON与JSON的区别
查看>>
我的友情链接
查看>>
Play Framework 模板里使用注入访问数据层
查看>>
Win2008学习(十一),解决Remote App Web访问的证书问题
查看>>
python 实现 自动oa 签到签退 发送邮件提醒
查看>>
今天打开阿里妈妈惊现 ¥50 元佣金
查看>>
Oracle 正确删除archivelog文件
查看>>
微信JS 关闭网页
查看>>
[AAuto]给百宝箱增加娱乐功能
查看>>