标签:重构 浅谈 val 原图 read Kruskal int
如何构建
当运行 \(Kruskal\) 算法找到当前一条可加入的边时,设左右端点在并查集中的所属位置和边权分别为 \(x,y,val\),在新图上新建一个节点 \(T\),并加入 \((T,x),(T,y)\) 两条边(可以直接加有向边,因为新图中的树的根和方向都固定了),并把 \(T\) 的点权赋为 \(val\),将并查集中 \(x,y\) 合并到 \(T\) 中,即为完成了一次加点构建。在新加了 \(n-1\) 个点后,新图中的就是一棵 \(Kruskal\) 重构树了(如果原图不连通,则新图为 \(Kruskal\) 重构树森林),其根节点为最后新建的新点(若是森林,则为并查集中所属位置等于自己的点)。
void kruskal(){
sort(e+1,e+1+m,[](edge x,edge y){return x.val>y.val;});
for(int i=1;i<=n;++i) ff[i]=i;
for(int i=1;i<=m;++i){
int x=find(e[i].from),y=find(e[i].to);
if(x!=y){
val[++cnt]=e[i].val;
ff[x]=ff[y]=ff[cnt]=cnt;
adde(cnt,x),adde(cnt,y);
}
}
}
一些手滑点:\(cnt\) 初值要赋为 \(n\),并查集要赋初值(老问题了)。
一些性质
\(Kruskal\) 重构树拥有一些奇妙的性质:(下文简称为“重构树”)
-
原图中的两点之间的路径中最小的最大边权为这两点在重构树上的 \(lca\) 的点权(这是在原图升序排序下的性质,降序排序则为最大的最小边权)(核心性质)
-
原图中的点在重构树中均是叶子结点
-
重构树是一个二叉树
-
重构树在原图边权升序排序下是一个大根堆,降序排序下是一个小根堆(由此性质可证得第一个性质)
一些运用
LuoguP1967 [NOIP2013 提高组] 货车运输
没学 \(Kruskal\) 重构树之前,这是一道神仙的树上倍增题(要在原图的最大生成树上跑),学了之后这就是一道简简单单的板子题了。
注意:这是 \(Kruskal\) 重构树森林,对所有的树根都要dfs预处理(数据太水导致我没有全部处理都过了)
当然还有更板子的题:BZOJ3732
#include<bits/stdc++.h>
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=5e4+5;
int n,m;
struct edge{int from,to,val;}e[N];
int ff[N];
int hd[N],nx[N],to[N],tote,val[N],cnt;
int dep[N],top[N],sz[N],son[N],fa[N];
void adde(int u,int v){
nx[++tote]=hd[u];to[tote]=v;hd[u]=tote;
nx[++tote]=hd[v];to[tote]=u;hd[v]=tote;
}
int find(int x){return ff[x]==x?x:ff[x]=find(ff[x]);}
void dfs1(int u,int father){
dep[u]=dep[father]+1;fa[u]=father;sz[u]=1;
for(int i=hd[u];i;i=nx[i]){
int v=to[i];
if(v==father) continue;
dfs1(v,u);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int anc){
top[u]=anc;
if(son[u]) dfs2(son[u],anc);
for(int i=hd[u];i;i=nx[i]){
int v=to[i];
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
return x;
}
void kruskal(){
sort(e+1,e+1+m,[](edge x,edge y){return x.val>y.val;});
for(int i=1;i<=n;++i) ff[i]=i;
for(int i=1;i<=m;++i){
int x=find(e[i].from),y=find(e[i].to);
if(x!=y){
val[++cnt]=e[i].val;
ff[x]=ff[y]=ff[cnt]=cnt;
adde(cnt,x),adde(cnt,y);
}
}
for(int i=1;i<=n*2-1;++i) if(ff[i]==i) dfs1(i,0),dfs2(i,i);
}
int main(){
cnt=n=read<int>(),m=read<int>();
for(int i=1;i<=m;++i){
int x=read<int>(),y=read<int>(),z=read<int>();
e[i]=(edge){x,y,z};
}
kruskal();
for(int Q=read<int>();Q;--Q){
int x=read<int>(),y=read<int>();
if(find(x)!=find(y)) puts("-1");
else printf("%d\n",val[LCA(x,y)]);
}
return 0;
}
LuoguP4197 Peaks
这题直接上在线做法,也可以过P7834 [ONTAK2010] Peaks 加强版
先构建出重构树,可以发现从 \(v\) 开始走边权不大于 \(x\) 的边能走到的区间,等价于在重构树上从 \(v\) 开始向上跳,每次只能走点权小于等于 \(x\) 的点,最后停在的那个结点的所有叶子结点即为能原图中走到的所有点。
把叶子结点按dfs序编号,考虑dfs一遍给每个结点都算出它的叶子结点区间(我这里用的是左开右闭的区间),\(x\) 向上跳的过程可以用倍增实现,找出区间后用主席树查找区间第 \(k\) 大即可。
#include<bits/stdc++.h>
#define id(u) lower_bound(b+1,b+1+len,a[u])-b
using namespace std;
template <class T>
inline T read(){
T r=0,f=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
return f?-r:r;
}
const int N=5e5+5;
int n,m,Q,ans;
struct edge{int from,to,val;}e[N];
int a[N],b[N],len;
int ff[N];
int f[N][25],L[N],R[N],num;
int hd[N],nx[N],to[N],val[N],tote,cnt;
int rt[N],sz[N*20],ls[N*20],rs[N*20],tot;
void adde(int u,int v){nx[++tote]=hd[u];to[tote]=v;hd[u]=tote;}
int find(int x){return ff[x]==x?x:ff[x]=find(ff[x]);}
void insert(int &y,int x,int l,int r,int p){
y=++tot,ls[y]=ls[x],rs[y]=rs[x],sz[y]=sz[x]+1;
if(l==r) return;
int mid=l+r>>1;
if(p<=mid) insert(ls[y],ls[x],l,mid,p);
else insert(rs[y],rs[x],mid+1,r,p);
}
int query(int y,int x,int l,int r,int k){
if(l==r) return l;
int mid=l+r>>1,d=sz[rs[y]]-sz[rs[x]];
if(k<=d) return query(rs[y],rs[x],mid+1,r,k);
else return query(ls[y],ls[x],l,mid,k-d);
}
void dfs(int u,int father){
f[u][0]=father;L[u]=num;
for(int i=1;i<=20;++i) f[u][i]=f[f[u][i-1]][i-1];
if(!hd[u]) ++num,insert(rt[num],rt[num-1],1,len,id(u));
for(int i=hd[u];i;i=nx[i]){
int v=to[i];
if(v==father) continue;
dfs(v,u);
}
R[u]=num;
}
void kruskal(){
for(int i=1;i<=n;++i) ff[i]=i;
sort(e+1,e+1+m,[](edge x,edge y){return x.val<y.val;});
for(int i=1;i<=m;++i){
int x=find(e[i].from),y=find(e[i].to);
if(x!=y){
val[++cnt]=e[i].val;
ff[cnt]=ff[x]=ff[y]=cnt;
adde(cnt,x),adde(cnt,y);
}
}
dfs(cnt,0);
}
void query(int x,int c,int k){
for(int i=20;i>=0;--i) if(f[x][i]&&val[f[x][i]]<=c) x=f[x][i];
if(R[x]-L[x]<k) ans=0,puts("-1");
else printf("%lld\n",ans=b[query(rt[R[x]],rt[L[x]],1,len,k)]);
}
signed main(){
cnt=n=read<int>(),m=read<int>(),Q=read<int>();
for(int i=1;i<=n;++i) b[i]=a[i]=read<int>();
sort(b+1,b+1+n);
len=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=m;++i){
int x=read<int>(),y=read<int>(),z=read<int>();
e[i]=(edge){x,y,z};
}
kruskal();
while(Q--){
int x=(read<int>()^ans)%n+1,y=read<int>()^ans,z=(read<int>()^ans)%n+1;
query(x,y,z);
}
return 0;
}
5个推荐暴切狼人
标签:重构,浅谈,val,原图,read,Kruskal,int 来源: https://www.cnblogs.com/Quick-Kk/p/Kruskaltree.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。