kruskal 重构树学习笔记
目录
- 前言
- kruskal 最小生成树
- kruskal 重构树
3.1 建树
3.2 性质 - 应用
1.前言
应老师要求,来写一篇关于 kruskal 重构树的学习笔记
2. kruskal 最小生成树
在学重构树之前,我们需要了解 kruskal 最小生成树。
kruskal 最小生成树算法的流程为:将边从小到大排序,从小到大枚举每一条边,如果这条边连接的 2 个顶点没有联通,那么就将这条边加入最小生成树中。最后选出的边组成的树就为最小生成树。
由于算法很基础,所以不多赘述。
3. kruskal 重构树
3.1 建树
kruskal 重构树其实与最小生成树类似,但它不是将一条边加入一个边集,而是新建一个点,将边所连的 2 个点与新点相连。
举个例子:
对于这个无向图:
它的最小生成树是(红边):
但我们要建 kruskal 重构树,所以我们在 kruskal 建生成树的时候,每遍历到一条生成树中的边,我们遍新建一个节点 \(tot\) ,将这个边所连的两个点集与 \(tot\) 相连。这个点的点权记做这条边的边权。
图形形象理解:
还是这个无向图:
首先我们按 kruskal 的步骤来,排序后选出最小边:
建重构树(方点是新加的点):
注意:这个 5 不是它的点权,是编号。点权 w[5]=2(就是 \(1 \rightarrow 2\) 这条边的边权)
然后选出第二条边,建重构树:
同理,即可建出整棵重构树
如此。kruskal 重构树就建好了。
3.2 性质
- 重构树是节点总数为 \(2n-1\) 的二叉树 。
- 重构树是一个根堆(最小生成树:大根堆,最大生成树:小根堆)
- 按最小生成树重构的重构树中任意两点 \(a,b\) 的路径中的最大边权为它们的 LCA 的点权。也就是原图中,\(a,b\) 路径中最大边权的最小值 。最大生成树同理。
- 如果原图不连通,会得到重构树森林。
- 只有叶子节点是原图中的点
这些性质在处理一些题时很有用。
4. 应用
P2245 星际导航
先建出kruskal重构树,然后找 \(u,v\) 的LCA。该点点权即为答案
可以说是模板题了。
双倍经验:P1967 货车运输
#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
using namespace std;
const int N=4e6+7;
int n,m;
inline int read()
{
register int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') w=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
s=s*10+ch-'0';
ch=getchar();
}
return s*w;
}
struct node
{
int to,nxt;
}e[N];
int head[N],cnt;
struct E
{
int fr,to,w;
}edge[N];
int q;
bool cmp(E x,E y)
{
return x.w<y.w;
}
void add(int u,int v)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
head[u]=cnt;
}
int fa[N],w[N];
int find(int x)
{
if(x==fa[x]) return x;
return fa[x]=find(fa[x]);
}
int tot;
void kruskal()
{
for(int i=1;i<=2*n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int fau=find(edge[i].fr),fav=find(edge[i].to);
if(fau==fav) continue;
fa[fau]=++tot;
fa[fav]=tot;
w[tot]=edge[i].w;
add(tot,fau);
// add(fau,tot);
add(tot,fav);
// add(fav,tot);
// cout<<tot<<" "<<fau<<" "<<fav<<endl;
}
}
int f[N],siz[N],son[N],dfn[N],id[N],top[N],dep[N];
void dfs1(int x)
{
siz[x]=1;
int maxx=-1;
for(int i=head[x];i;i=e[i].nxt)
{
int v=e[i].to;
if(!dep[v])
{
dep[v]=dep[x]+1;
f[v]=x;
dfs1(v);
siz[x]+=siz[v];
if(siz[v]>maxx) maxx=siz[v],son[x]=v;
}
}
}
void dfs2(int x,int d)
{
top[x]=d;
if(!son[x]) return;
dfs2(son[x],d);
for(int i=head[x];i;i=e[i].nxt)
{
int v=e[i].to;
if(dep[v]>dep[x]&&son[x]!=v)
dfs2(v,v);
}
}
inline int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=f[top[x]];
}
if(dep[x]<dep[y]) return x;
return y;
}
int main()
{
n=read();
m=read();
tot=n;
for(int i=1;i<=m;i++)
{
edge[i].fr=read();
edge[i].to=read();
edge[i].w=read();
}
sort(edge+1,edge+1+m,cmp);
kruskal();
for(int i=tot;i>=1;i--)
{
if(!dep[i])
{
dfs1(i);
dfs2(i,i);
}
}
q=read();
while(q--)
{
int u=read(),v=read();
if(find(u)!=find(v))
{
cout<<"impossible"<<endl;
continue;
}
int lca=LCA(u,v);
// cout<<lca<<endl;
cout<<w[lca]<<endl;
}
return 0;
}
P4768 [NOI2018] 归程
我们首先要求出每个点到 \(1\) 号点的最小花费 (dijkstra 最短路预处理) 。然后建出 kruskal 重构树,再维护以每个点作为根节点时子树中距离 1 号点的最小花费,直接 dfs 搞定。对于 u,我们直接在重构树上倍增即可。
#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
using namespace std;
const int N=2e6+7;
struct node
{
int fr,to,nxt,w,h;
}e[N],tr[N];
int vis[N],d[N],head[N];
priority_queue<pair<ll,int> > q;
int cnt=0;
int fa[N],dot[N];
int n,m,t;
void add(int u,int v,int w)
{
e[++cnt].to=v;
e[cnt].nxt=head[u];
e[cnt].w=w;
head[u]=cnt;
}
void dijkstra(int s)
{
memset(vis,0,sizeof(vis));
memset(d,0x3f,sizeof(d));
d[s]=0;
q.push(make_pair(0,s));
while(!q.empty())
{
int u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(d[u]+e[i].w<d[v])
{
d[v]=d[u]+e[i].w;
q.push(make_pair(-d[v],v));
}
}
}
}
bool cmp(node x,node y){return x.h>y.h;}
int find(int x)
{
if(fa[x]==x) return fa[x];
return fa[x]=find(fa[x]);
}
int tot;
int minn[N];
int bz[N][24];
void dfs(int x)
{
minn[x]=d[x];
for(int i=head[x];i;i=e[i].nxt)
{
bz[e[i].to][0]=x;
dfs(e[i].to);
minn[x]=min(minn[x],minn[e[i].to]);
}
}
void kruskra()
{
sort(tr+1,tr+1+m,cmp);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int fau=find(tr[i].fr),fav=find(tr[i].to);
if(fau==fav) continue;
dot[++tot]=tr[i].h;
add(tot,fau,0);
add(tot,fav,0);
fa[fau]=tot;
fa[fav]=tot;
fa[tot]=tot;
}
}
int main()
{
cin>>t;
while(t--)
{
memset(vis,0,sizeof(vis));
memset(head,0,sizeof(head));
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int a,b,c,d;
cin>>a>>b>>c>>d;
add(a,b,c);
add(b,a,c);
tr[i].fr=a,tr[i].to=b;
tr[i].h=d;
}
dijkstra(1);
tot=n;
cnt=0;
memset(head,0,sizeof(head));
kruskra();
memset(minn,0x3f,sizeof(minn));
memset(bz,0,sizeof(bz));
dfs(tot);
for(int i=1;(1<<i)<=tot;i++)
for(int j=1;j<=tot;j++)
bz[j][i]=bz[bz[j][i-1]][i-1];
int q,k,s;
cin>>q>>k>>s;
int lastans=0;
while(q--)
{
int v0,p0;
cin>>v0>>p0;
int v=(v0+k*lastans-1)%n+1;
int p=(p0+k*lastans)%(s+1);
for(int i=22;i>=0;i--)
if(bz[v][i]&&dot[bz[v][i]]>p)
v=bz[v][i];
cout<<minn[v]<<endl;
lastans=minn[v];
}
}
return 0;
}
P4197 Peaks
先建出重构树,然后建出主席树,对于每个询问,答案是以 \(x\) 为根节点的子树内的第 \(k\) 小
对于求 \(x\) ,就是前面讲的。
#include<bits/stdc++.h>
#define ll long long
#define endl "\n"
using namespace std;
const int N=2e6+7;
struct node
{
int fr,to,nxt,w;
}edge[N],e[N];
int head1[N],cnt1;
int h[N],hh[N];
struct treee
{
int l,r,w,L,R;
}t[N*4],tt[N*4];
void add1(int u,int v,int w)
{
edge[++cnt1].to=v;
edge[cnt1].fr=u;
edge[cnt1].nxt=head1[u];
edge[cnt1].w=w;
head1[u]=cnt1;
}
int tot,cnt;
bool cmp(node a,node b)
{
return a.w<b.w;
}
int fa[N];
int n,m,q;
int f[N][32];
int weight[N];
int rot[N];
void dfs(int x,int fat)
{
// cout<<x<<endl;
f[x][0]=fat;
for(int i=1;i<=20;i++)
f[x][i]=f[f[x][i-1]][i-1];
if(!t[x].w)
{
weight[++cnt]=h[x];
// cout<<weight[cnt]<<" "<<cnt<<endl;
t[x].L=t[x].R=cnt;
return ;
}
dfs(t[x].l,x);
dfs(t[x].r,x);
t[x].L=t[t[x].l].L;
t[x].R=t[t[x].r].R;
}
int find(int x)
{
if(x==fa[x])
return x;
return fa[x]=find(fa[x]);
}
void kruskal()
{
// cout<<tot<<endl;
sort(edge+1,edge+1+m,cmp);
for(int i=1;i<=m;i++)
{
int fau=find(edge[i].fr),fav=find(edge[i].to);
// cout<<i<<endl;
if(fau==fav) continue;
// cout<<edge[i].fr<<"a "<<edge[i].to<<"a "<<edge[i].w<<endl;
++tot;
fa[fau]=fa[fav]=tot;
t[tot].l=fau;
t[tot].r=fav;
t[tot].w=edge[i].w;
// cout<<tot<<" a"<<t[tot].w<<endl;
}
// cout<<tot<<endl;
}
int build(int l,int r)
{
int now=++cnt;
tt[now].w=0;
if(l==r)
return now;
int mid=(l+r)/2;
tt[now].l=build(l,mid);
tt[now].r=build(mid+1,r);
return now;
}
int add(int x,int l,int r,int k,int val)
{
int now=++cnt;
tt[now].l=tt[x].l;
tt[now].r=tt[x].r;
tt[now].w=tt[x].w;
if(l==r)
{
tt[now].w+=val;
return now;
}
int mid=(l+r)/2;
if(k<=mid) tt[now].l=add(tt[now].l,l,mid,k,val);
else tt[now].r=add(tt[now].r,mid+1,r,k,val);
tt[now].w=tt[tt[now].l].w+tt[tt[now].r].w;
return now;
}
int query(int L,int R,int l,int r,int k)
{
if(l==r) return l;
int val=tt[tt[L].l].w-tt[tt[R].l].w;
int mid=(l+r)/2;
if(val>=k) return query(tt[L].l,tt[R].l,l,mid,k);
else return query(tt[L].r,tt[R].r,mid+1,r,k-val);
}
int main()
{
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
{
cin>>h[i];
hh[i]=h[i];
}
sort(hh+1,hh+1+n);
int loon=unique(hh+1,hh+1+n)-hh-1;
for(int i=1;i<=n;i++)
h[i]=lower_bound(hh+1,hh+1+loon,h[i])-hh;
for(int i=1;i<=m*2;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
add1(a,b,c);
}
tot=n;
kruskal();
dfs(tot,0);
rot[0]=build(1,n);
for(int i=1;i<=n;i++)
rot[i]=add(rot[i-1],1,n,weight[i],1);
while(q--)
{
int v,x,k;
cin>>v>>x>>k;
for(int i=20;i>=0;i--)
if(t[f[v][i]].w<=x&&f[v][i])
v=f[v][i];
int len=t[v].R-t[v].L+1;
if(len<k)
cout<<-1<<endl;
else
{
k=len-k+1;
int way=query(rot[t[v].R],rot[t[v].L-1],1,n,k);
cout<<hh[way]<<endl;
}
}
return 0;
}
/*
5 4 5
1 2 3 4 5
1 2 5
2 3 5
3 4 5
4 5 5
1 5 3
2 5 5
3 5 4
4 5 2
5 5 1
*/
P4899 [IOI2018] werewolf 狼人
先以节点编号大小建 2 棵重构树,然后建出树状数组,对于每个询问,就是在 \(dfn[L[i]]\) 与 \(dfn[R[i]]\) 间数点
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,m,q;
const int N=1e6+7;
struct node
{
int to,nxt,w,fr;
}e1[N],e2[N],e[N];//e1 Öع¹Ê÷1 e2 Öع¹Ê÷ 2 e Ôͼ
int head1[N],head2[N],cnt1,cnt2,cnt;
void add1(int u,int v)//µÚÒ»¿Ã
{
e1[++cnt1].to=v;
e1[cnt1].nxt=head1[u];
head1[u]=cnt1;
}
void add2(int u,int v)//µÚ¶þ¿Ã
{
e2[++cnt2].to=v;
e2[cnt2].nxt=head2[u];
head2[u]=cnt2;
}
int fa1[N],fa2[N];
bool cmp1(node a,node b)
{
return a.w>b.w;
}
bool cmp2(node a,node b)
{
return a.w<b.w;
}
int find1(int x)//1
{
if(x==fa1[x]) return x;
return fa1[x]=find1(fa1[x]);
}
int find2(int x)//2
{
if(x==fa2[x]) return x;
return fa2[x]=find2(fa2[x]);
}
int tot1,tot2;//1,2Öع¹Ê÷µÄµãÊý
int w1[N],w2[N];
void kruskal1()//µÚÒ»¿Ã
{
sort(e+1,e+1+m,cmp1);
for(int i=1;i<=m;i++)
{
int fau=find1(e[i].fr),fav=find1(e[i].to);
if(fau==fav) continue;
tot1++;
add1(tot1,fau);
add1(tot1,fav);
w1[tot1]=e[i].w;
fa1[fav]=tot1;
fa1[fau]=tot1;
fa1[tot1]=tot1;
}
}
void kruskal2()//µÚ¶þ¿Ã
{
sort(e+1,e+1+m,cmp2);
for(int i=1;i<=m;i++)
{
int fau=find2(e[i].fr),fav=find2(e[i].to);
if(fau==fav) continue;
tot2++;
add2(tot2,fau);
add2(tot2,fav);
w2[tot2]=e[i].w;
fa2[fav]=tot2;
fa2[fau]=tot2;
fa2[tot2]=tot2;
}
}
int dfn1[N],dfn2[N],low1[N],low2[N],c1,c2;//1 µÚÒ»¸öÖع¹Ê÷£¬2 µÚ¶þ¸öÖع¹Ê÷
int f1[N][21],f2[N][21];
void dfs1(int x,int fa)
{
dfn1[x]=++c1;
f1[x][0]=fa;
for(int i=1;i<=20;i++)
{
f1[x][i]=f1[f1[x][i-1]][i-1];
}
for(int i=head1[x];i;i=e1[i].nxt)
{
int v=e1[i].to;
if(v==fa) continue;
dfs1(v,x);
}
low1[x]=c1;
}
void dfs2(int x,int fa)
{
dfn2[x]=++c2;
f2[x][0]=fa;
for(int i=1;i<=20;i++)
f2[x][i]=f2[f2[x][i-1]][i-1];
for(int i=head2[x];i;i=e2[i].nxt)
{
int v=e2[i].to;
if(v==fa) continue;
dfs2(v,x);
}
low2[x]=c2;
}
int query1(int x,int k)
{
for(int i=20;i>=0;i--)
if(f1[x][i]&&w1[f1[x][i]]>=k)
x=f1[x][i];
return x;
}
int query2(int x,int k)
{
for(int i=20;i>=0;i--)
if(f2[x][i]&&w2[f2[x][i]]<=k)
x=f2[x][i];
return x;
}
int t[N*4];
int lowbit(int x)
{
return x&(-x);
}
int tot=0;//Ê÷×´Êý×éÊýµã
void add(int x,int k)
{
for(;x<=tot;x+=lowbit(x))
t[x]+=k;
}
int sum(int x)
{
int ans=0;
for(;x;x-=lowbit(x))
ans+=t[x];
return ans;
}
struct dot
{
int x,y,id,d;
}a[N];
inline bool cmp(dot x,dot y)
{
if(x.x==y.x)
{
if(x.y==y.y)
return x.id<y.id;
return x.y<y.y;
}
return x.x<y.x;
}
int fin[N][5];
int main()
{
cin>>n>>m>>q;
for(int i=1;i<=n;i++)
fa1[i]=fa2[i]=i;
tot1=tot2=n;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
u++;v++;
e[++cnt].fr=u;
e[cnt].to=v;
e[cnt].w=min(u,v);
}
kruskal1();
for(int i=1;i<=m;i++)
e[i].w=max(e[i].fr,e[i].to);
kruskal2();
dfs1(tot1,tot1);
dfs2(tot2,tot2);
for(int i=1;i<=n;i++)
a[++tot]=(dot){dfn1[i],dfn2[i],0,0};
// for(int i=1;i<=tot1;i++) cout<<dfn1[i]<<" "<<low1[i]<<" ";
// cout<<endl;
// for(int i=1;i<=tot2;i++) cout<<dfn2[i]<<" "<<low2[i]<<" ";
for(int i=1;i<=q;i++)
{
int st,en,l,r;
cin>>st>>en>>l>>r;
st++;en++;l++;r++;
st=query1(st,l);
en=query2(en,r);
int x1=dfn1[st],x2=low1[st],y1=dfn2[en],y2=low2[en];
a[++tot]=(dot){x1-1,y2,i,1};
a[++tot]=(dot){x2,y2,i,2};
a[++tot]=(dot){x2,y1-1,i,3};
a[++tot]=(dot){x1-1,y1-1,i,4};
}
sort(a+1,a+1+tot,cmp);
// cout<<tot<<endl;
for(int i=1;i<=tot;i++)
{
// cout<<a[i].x<<" "<<a[i].y<<" "<<a[i].id<<" "<<a[i].d<<endl;
if(a[i].id==0) {add(a[i].y,1);}
else fin[a[i].id][a[i].d]+=sum(a[i].y);
}
// for(int i=1;i<=tot1;i++)
// cout<<sum(i)<<endl;
for(int i=1;i<=q;i++)
{
int val=fin[i][2]-fin[i][1]-fin[i][3]+fin[i][4];
if(val>0) cout<<1<<endl;
else cout<<0<<endl;
}
return 0;
}