Kruskal重构树学习笔记
简介
根据算法名称,我们知道他跟 Kruskal 算法应有关联,所以得先学 Kruskal 算法。
至于重构树的用处,只能说很有用而且有很强大的性质。
可以解决形如:从 \(x\) 到 \(y\) 的路径,不经过(大于\小于) \(val\) 类的问题。
构建
依据 Kruskal 算法,贪心地选取边 \((x,y)\) 时,新建一个 \(now\) 节点,然后将 \(now\) 与 \(find(x)\) ,\(now\) 与 \(find(y)\) 分别连边,\(now\) 的点权就是原先 \(x\) 与 \(y\) 的边权。
性质
根据构建的方式,就可以得到几个很强大的性质。
- 重构树是一棵二叉树。
- 具有堆的性质,具体的,如果是最小\大生成树,就是大\小根堆。
- 两点的 LCA 是重构树上两点间路径的最大值\最小值。
具体而言,看例题。
例题
货车运输
发现 \(x\) 到 \(y\) 的路径中,只需要保留一条最大的即可,然后就可以对整个图做最大生成树,然后根据上面的性质,两点的 LCA 就是我们想要的答案,直接新开数组维护即可。
code:
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1e5+50;
const int M=5e5+50;
int n,m,q;
struct Edge{
int to,nxt,w;
}e[2*M];
int head[2*M],tot;
struct node{
int x,y,w;
}a[2*M];
bool cmp(node a,node b){
return a.w>b.w;
}
void add(int u,int v,int w){
e[++tot].to=v;
e[tot].nxt=head[u];
e[tot].w=w;
head[u]=tot;
return ;
}
int dep[N],fa[N],f[N][30],w[N][30];
bool vis[N];
int find(int x){
if(fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
void dfs(int u){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[v]) continue;
vis[v]=1;
dep[v]=dep[u]+1;
f[v][0]=u;
w[v][0]=e[i].w;
dfs(v);
}
return ;
}
void kruskal(){
sort(a+1,a+1+m,cmp);
for(int i=1;i<=n;i++){
fa[i]=i;
}
for(int i=1;i<=m;i++){
int p=find(a[i].x);
int q=find(a[i].y);
if(p==q) continue;
fa[p]=q;
add(a[i].x,a[i].y,a[i].w);
add(a[i].y,a[i].x,a[i].w);
}
return ;
}
int lca(int x,int y){
if(find(x)!=find(y)) return -1;
int ans=2e9;
if(dep[x]<dep[y]) swap(x,y);
for(int i=25;i>=0;i--){
if(dep[f[x][i]]>=dep[y]){
ans=min(ans,w[x][i]);
x=f[x][i];
}
}
// cout<<"q"<<ans<<endl;
if(x==y) return ans;
for(int i=25;i>=0;i--){
if(f[x][i]!=f[y][i]){
ans=min(ans,min(w[x][i],w[y][i]));
x=f[x][i];
y=f[y][i];
}
}
ans=min(ans,min(w[x][0],w[y][0]));
return ans;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
int x,y,w;
cin>>x>>y>>w;
a[i].x=x;
a[i].y=y;
a[i].w=w;
}
kruskal();
for(int i=1;i<=n;i++){
if(!vis[i]){
dep[i]=1;
vis[i]=1;
dfs(i);
f[i][0]=i;
w[i][0]=2e9;
}
}
for(int i=1;i<=25;i++){
for(int j=1;j<=n;j++){
f[j][i]=f[f[j][i-1]][i-1];
w[j][i]=min(w[f[j][i-1]][i-1],w[j][i-1]);
}
}
cin>>q;
for(int i=1;i<=q;i++){
int x,y;
cin>>x>>y;
int ans=lca(x,y);
cout<<ans<<'\n';
}
return 0;
}
Stranded Far From Home (Day2)
仔细阅读题目后,观察到关键性质:
如果我们想令颜色 \(x\) 成为最后颜色,那么需要令 \(x\) 先吞掉所有他能吞掉的,然后再去其它颜色。
具体而言,当我们构建完重构树后,另 \(x\) 与 \(y\) 中点权较大值为新节点 \(now\) 的点权。
对于重构树的叶子节点,就表示每个村庄的人数。
对于非叶子节点 \(x\) ,我们可以认为它表示某一个颜色已经吞掉了 \(x\) 所包含的所有子树村庄。
而 \(x\) 想吞并别的村庄,需要 \(x\) 的子树和大于等于它的父亲,进而吞掉父亲节点,以及整棵以父亲节点为根的子树。
最后只需要知道是否能一直吞,吞到根节点即可。
code:
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=2e5+10;
const int inf=1e9;
int n,m,tot;
long long num[2*maxn],val[2*maxn];
int fa[maxn*2+10];
int ans[maxn*2];
vector<int> vec[2*maxn];
struct node{
int x,y;
long long w;
}e[maxn];
bool cmp(node a,node b){
return a.w<b.w;
}
int find(int x){
if(x==fa[x]) return x;
else return fa[x]=find(fa[x]);
}
void dfs(int u,int f){
if(num[u]>=val[f]){
ans[u]=(ans[u]||ans[f]);
}
for(int i=0;i<(int)vec[u].size();i++){
if(vec[u][i]==f) continue;
dfs(vec[u][i],u);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>num[i];
}
for(int i=1;i<=m;i++){
cin>>e[i].x>>e[i].y;
e[i].w=max(num[e[i].x],num[e[i].y]);
}
for(int i=1;i<=2*n;i++){
fa[i]=i;
}
sort(e+1,e+1+m,cmp);
tot=n;
for(int i=1;i<=m;i++){
int x=find(e[i].x);
int y=find(e[i].y);
if(x==y) continue;
fa[x]=fa[y]=++tot;
num[tot]=num[x]+num[y];
val[tot]=e[i].w;
vec[tot].push_back(x);
vec[tot].push_back(y);
}
ans[tot]=1;
dfs(tot,tot);
for(int i=1;i<=n;i++){
cout<<ans[i];
}
return 0;
}
[NOI2018] 归程
SPFA最短路好题
发现水位线比较难处理,处理完水位线后就是简单的最短路了。
既然水位线难处理,那就先说处理完水位线怎么做:预处理 \(1\) 到其余点的最短路,然后在处理完后的点集中选取 \(v\) 可到达节点中取最小 \(dis\) 。
对于海拔做最大生成树后重构,发现如果节点 \(x\) 没有被淹,那么它的所有子树也没有被淹,也就是可以无代价地去子树中任何一点。
多开一个数组 p 维护重构树上每个点到达节点 \(1\) 的最小值,每个节点的答案就是以节点为根的子树最小值,DFS 维护一下,倍增跳就行。
code:
#include<iostream>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=400010;
const int M=800010;
const int inf=2e9;
int T,n,m,q,k,s;
int dis[N];
bool vis[N];
int dep[N],f[N][30];
int lastans;
struct node{
int u,v,l,a;
}e[M],p[N<<1];
bool cmp(node a,node b){
return a.a>b.a;
}
//e是边,p维护最短路径和
struct Edge{
int to,nxt,val;
}G[M];
int head[N],totx;
//G是原图
struct Tree{
int to,nxt;
}tr[M];
int hd[N],cntx;
//tr是重构树
int fa[N<<1];
int find(int x){if(x==fa[x]) return x;return fa[x]=find(fa[x]);}
void addtr(int u,int v){
tr[++cntx].to=v;
tr[cntx].nxt=hd[u];
hd[u]=cntx;
return ;
}
void addG(int u,int v,int w){
G[++totx].to=v;
G[totx].nxt=head[u];
G[totx].val=w;
head[u]=totx;
return ;
}
struct Dij{
int x,w;
bool operator < (const Dij a) const{
return a.w<w;
}
};
//dij时优先队列用
void dij(){
priority_queue<Dij> que;
que.push({1,0});
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++) dis[i]=inf;
dis[1]=0;
while(!que.empty()){
Dij u=que.top();que.pop();
if(vis[u.x]) continue;
vis[u.x]=1;
for(int i=head[u.x];i;i=G[i].nxt){
int v=G[i].to;
if(vis[v]) continue;
dis[v]=min(dis[v],dis[u.x]+G[i].val);
que.push({v,dis[v]});
}
}
for(int i=1;i<=n;i++) p[i].l=dis[i];
return ;
}
void dfs(int u,int fa){
f[u][0]=fa;
dep[u]=dep[fa]+1;
for(int i=1;i<=19;i++) f[u][i]=f[f[u][i-1]][i-1];
for(int i=hd[u];i;i=tr[i].nxt){
int v=tr[i].to;
dfs(v,u);
p[u].l=min(p[u].l,p[v].l);
}
return ;
}
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;
}
void Kruskal(){
for(int i=1;i<=(n<<1);i++) fa[i]=i;
int tot=0,cnt=n;
sort(e+1,e+1+m,cmp);
for(int i=1;i<=m;i++){
int fu=find(e[i].u);
int fv=find(e[i].v);
if(fu!=fv){
addtr(++cnt,fu);
addtr(cnt,fv);
fa[fu]=cnt;fa[fv]=cnt;
p[cnt].a=e[i].a;
++tot;
}
if(tot==n-1) break;
}
dfs(cnt,0);
while(q--){
int x,y;
cin>>x>>y;
x=(x+k*lastans-1)%n+1;
y=(y+k*lastans)%(s+1);
lastans=query(x,y);
cout<<lastans<<'\n';
}
}
void init(){
memset(e,0,sizeof(e));
memset(head,0,sizeof(head));
memset(hd,0,sizeof(hd));
memset(f,0,sizeof(f));
cntx=0,totx=0;
return ;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>T;
while(T--){
lastans=0;
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].l>>e[i].a;
addG(e[i].u,e[i].v,e[i].l);
addG(e[i].v,e[i].u,e[i].l);
}
for(int i=n;i<=(n<<1);i++) p[i].l=inf;
dij();
cin>>q>>k>>s;
Kruskal();
}
return 0;
}
什么你问我为什么是 dij ,不是 SPFA 最短路好题吗。

浙公网安备 33010602011771号