笔记-Kruskal重构树(一)

U12讲笔记

树链点权最值问题

暴力:对于随机数据,单次查询平均复杂度 \(O(\log n)\)

目标:对于最差情况,单次查询复杂度 \(O(\log n)\)

倍增(\(\rm binary \; lifting\)):预处理 ST 表(稀疏表), \(\rm p[u][i]\) 代表 \(u\) 的第 \(2^i\) 个祖先,\(\rm st[u][i]\) 代表从 \(u\) 开始往上爬的共 \(2^i\) 个节点的点权最值

注意:

  • \(\rm p[u][0]\)\(u\) 的父亲
  • \(\rm st[u][0]\)\(u\) 本身
  • \(\rm st[u][i]\) 不包含 \(\rm p[u][i]\)

Code

const int N=2e5+10;
vector<int> son[N];
int L,n,m,d[N],p[N][30],st[N][30];
void dfs(int u){
    d[u]=d[p[u][0]]+1;
    for(int i=1;i<=L;i++){
        p[u][i]=p[p[u][i-1]][i-1];
        st[u][i]=max(st[u][i-1],st[p[u][i-1]][i-1]);
    }
    for(int i=0;i<son[u].size();i++)
        dfs(son[u][i]);
}
int query(int u,int k){
    int ans=st[u][0];
    for(int i=0;i<=L;i++)
        if((1<<i)&k){
            ans=max(ans,st[p[u][0]][i]);
            u=p[u][i];
        }
   	return ans;
}
cin>>n>>m;
for(int u=2;u<=n;u++){
    cin>>p[u][0];
    son[p[u][0]].push_back(u);
}
for(int u=1;u<=n;u++)cin>>st[u][0];
L=log(n)/log(2)+1;
dfs(1);
for(int i=1;i<=m;i++){
    int x,k;
    cin>>x>>k;
    k=min(k,d[x]-1);
    cout<<query(x,k)<<endl;
}

KK920

最长边最短路问题

算法列举

方法1:二分答案+连通性判断

方法2:联系 MST : Kruskal 变种

推论: MST 上任意两个点之间的路径,就是它们在原图上的最长边最短路径

方法3:联系 MST : Prim 变种 / Dijkstra 变种

KK3176

题面

题目描述

有一个 \(n\) 个点 \(m\) 条边的无向连通图,边长都给定,现在有 \(K\) 个问询,格式是 \(\rm A \quad B\) ,表示从 \(A\) 号节点走到 \(B\) 号节点的所有路径中,最长的边最小值是多少。

输入格式 path.in

第一行输入三个正整数 \(n,m,k\) ,接下来 \(m\) 行,每行包含三个正整数 \(u,v,w\) 代表 \(u\) 号节点和 \(v\) 号节点之间有一条长度为 \(w\) 的边。接着 \(k\) 行问询,每行两个正整数 \(A,B\) 表示问询从 \(A\) 号节点走到 \(B\) 号节点的所有路径中,最长的边最小值是多少 。

输出格式 path.out

对于每个问询,输出一行答案,每行包含一个整数。

输入样例

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

输出样例

5

5

5

4

4

7

4

5

数据范围

算法1

利用上一题方法二的推论:

先求出 MST

然后问题转换为 MST 树上路径最大边权问询

倍增 ST 表

Code

#include<bits/stdc++.h>
using namespace std;

void dfs(int u,int fa){
    tI[u]=++timer;
    d[u]=d[fa]+1;
    p[u][0]=fa;
    st[u][0]=val[u];
    for(int i=1;i<=L;i++){
        p[u][i]=p[p[u][i-1]][i-1];
        st[u][i]=max(st[u][i-1],st[p[u][i-1]][i-1]);
    }
    for(int i=0;i<to[u].size();i++){
        int v=to[u][i];
        if(v==fa)continue;
        val[v]=w[u][i];
        dfs(v,u);
    }
    tO[u]=++timer;
}

int lca(int u,int v){
    
}

int id[N];
int root(int u){return u==id[u]?id[u]:id[u]=root(id[u]);}

int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
        cin>>e[i].u>>e[i].v>>e[i].w;
   	sort(e+1,e+1+m,cmp);
    for(int u=1;u<=n;u++)id[u]=u;
    for(int i=1;i<=m;i++){
        int ru=root(e[i].u);
        int rv=root(e[i].v);
        if(ru==rv)continue;
        id[ru]=rv;
        to[e[i].u].push_back(e[i].v);
        to[e[i].v].push_back(e[i].u);
        w[e[i].u].push_back(e[i].w);
        w[e[i].v].push_back(e[i].w);
    }
    
    L=log(n)/log(2)+1;
    dfs(1,0);
    for(int i=1;i<=k;i++){
        int u,v;
        cin>>u>>v;
        int a=lca(u,v);
        int ans_u=0,ans_v=0;
        if(u!=a)ans_u=query(u,d[u]-d[a]-1);
        if(v!=a)ans_v=query(v,d[v]-d[a]-1);
        cout<<max(ans_u,ans_v);
    }
}

算法2-Kruskal 重构树

Kruskal 算法找 MST 时,合并操作对应二叉树

例子:

每一个附加点代表一个连通块,它的点权代表该连通块的最长边

性质:

  1. 附加点的个数一定是 \(n-1\)
  2. 根节点的编号一定是 \(2n-1\)
  3. 每一个附加点恰好有两个儿子
  4. 点权满足大根堆的性质(叶子没有点权,故不计在内)
  5. 原图上两点之间的最长边最短路就是新图两点间的 LCA 点权

易错点:新图的点数=原图点数*2-1

Code

void dfs(int u,int fa){
    st[u][0]=val[u];
    p[u][0]=fa;
    d[u]=d[fa]+1;
    tI[u]=++timer;
    for(int i=1;i<=L;i++){
        p[u][i]=p[p[u][i-1]][i-1];
        st[u][i]=max(st[u][i-1],st[p[u][i-1]][i-1]);
    }
    
}

int main(){
    cin>>n>>m>>k;
    for(int i=1;i<=m;i++)
        cin>>e[i].u>>e[i].v>>e[i].w;
   	sort(e+1,e+1+m,cmp);
    for(int i=1;i<=2*n-1;i++)id[i]=i;
    int nV=n;
    for(int i=1;i<=m;i++){
        int ru=root(e[i].u);
        int rv=root(e[i].v);
        if(ru==rv)continue;
        ++nV;
        id[ru]=id[rv]=nV;
        to[nV].push_back(ru);
        to[nV].push_back(rv);
        val[nV]=e[i].w;
    }
    
    L=log(n)/log(2)+1;
    dfs(nV,0);
    for(int i=1;i<=k;i++){
        int u,v;
        cin>>u>>v;
        cout<<val[lca(u,v)]<<endl;
    }
}

KK1164

题面

题目描述

\(n\) 座城市 \(m\) 条双向道路,每一条道路对车辆都有限重。现在有 \(q\) 辆货车在运送货物,司机们想知道每辆车在不超过限重的前提下,最多能运送多重的货物。

输入文件 truck.in

第一行包含两个整数 \(n,m\) 。接下来 \(m\) 行,每行三个整数 \(u,v,w\) 表示有一条连接 \(u\) 号城市和 \(v\) 号城市的道路限重为 \(w\) ,保证 \(u \neq v\) 但是两个城市之间可能有多条道路。接下来一行包含一个整数 \(q\), 表示有 \(q\) 个问询。接下来 \(q\) 行,每行两个整数 \(x,y\) 表示一辆货车从 \(x\) 城市到 \(y\) 城市运送货物,保证 \(x \neq y\)

输出文件 truck.out

输出共 \(q\) 行,每行一个整数,表示对于没一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出 -1 。

输入样例

4 3

1 2 4

2 3 3

3 1 1

3

1 3

1 4

1 3

输出样例

数据范围

算法分析

易错点:

  1. 要求路径的最小边权最大化---------改排序规则
  2. 图不一定连通
  3. 有重边

Code

#include<bit/stdc++.h>
using namespace std;

void dfs(int u,int fa){
    
}

int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++)
        cin>>e[i].u>>e[i].v>>e[i].w;
    sort(e+1,e+1+m,cmp);
    for(int u=1;u<=n*2;u++)id[u]=u;
    int nV=n;
    for(int i=1;i<=m;i++){
        int ru=root(e[i].u);
        int rv=root(e[i].v);
        if(ru==rv)continue;
        ++nV;
        id[ru]=id[rv]=nV;
        to[nV].push_back(ru);
        to[nV].push_back(rv);
        val[nV]=e[i].w;
    }
    
    L=log(nV)/log(2)+1;
    for(int u=n+1;u<=nV;u++)
        if(id[u]==u)
            dfs(u,0);
    cin>>q;
    for(int i=1;i<=q;i++){
        int u,v;
        cin>>u>>v;
        int ru=root(u);
        int rv=root(v);
        if(ru==rv)
            cout<<w[lca(u,v)]<<endl;
        else
            cout<<-1<<endl;
    }
}
posted @ 2023-07-12 14:58  hsfzbzjr  阅读(18)  评论(0编辑  收藏  举报