知识点 - 次小生成树

知识点 - 次小生成树

解决问题类型:

严格次小生成树

非严格次小生成树

复杂度:

O ( n l o g 2 n + m l o g 2 m ) O(nlog_2n + mlog_2m) O(nlog2n+mlog2m)

(倍增 O ( n l o g n n ) O(nlog_nn) O(nlognn)+Kruskal O ( m l o g 2 m + m α ( n ) ) O(mlog_2m+m\alpha(n)) O(mlog2m+mα(n))

实现

Kruskal是按照边权排序,按照贪心的思路一条一条的加进树边,所以如果要求出次小生成树,那么我们可以放弃一条小的边不选,加入另一条边使图联通。

如果我们"放弃"了两条边,那么只"放弃"一条边的生成树的边权和显然小于"放弃"两条边的生成树的边权和。

所以求出(非)严格次小生成树的朴素算法为:先建立一棵最小生成树。再枚举删去最小生成树上的每一条路径,再在新构成的生成树中选出一棵边权之和最小生成树的即可。

时间复杂度为 O ( n m l o g m ) O(nmlogm) O(nmlogm)。当然这种算法还不够优秀,我们可以继续优化

非严格次小生成树我们可以这样优化:

枚举边的时候,枚举没有被并入到最小生成树的边(我们将把这条边加入到生成树中,显然现在的图形不再是一棵树,所以我们要原图中删去一条最大边,使新图仍然联通)

加入边权值为 W 1 W1 W1。查询树上每一条的路径,在路径选取一个边权最大值 W 2 W2 W2。则当前枚举的答案为 W − W 2 + W 1 W−W2+W1 WW2+W1(W为最小生成树的边权之和)

枚举所有的边之后,取最小值即可。

我们可以用倍增/树剖/LCT来实现查询树上最大值的操作,故复杂度为: O ( m l o g n ) O(mlogn) O(mlogn)

再来看看严格次小生成树

不难发现:求非严格最小生成树时,枚举一条边 W 1 W1 W1,之后再寻找一条生成树上的最大边 W 2 W2 W2

显然 W 1 ≥ W 2 W1≥W2 W1W2,因此可能由此得到的次小生成树并非严格。所以我们可以在每次查询时,需要找到严格次小的 W 1 W1 W1,即 W 1 > W 2 W1>W2 W1>W2,这样我们就可以得到严格次小生成树了

维护仍可以用倍增或者树剖思想。

这里介绍树剖的思路:

先用线段树维护区间最小值与严格次小值,如果是叶子节点最大值就设为他本身,次大值设为0

合并的时候把两个区间的这四个值(两个最大值与两个次大值)排序,再寻找最大值与严格次小值即可。

例题

P4180 【模板】严格次小生成树[BJWC2010]

代码 树链剖分实现

//树链剖分实现
#include<bits/stdc++.h>
using namespace std;
#define re register
#define il inline
#define int long long //把所有int转成longlong
#define debug printf("Now is line %d\n",__LINE__);
il int read()
{
    re int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
    while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}//快读 
#define maxm 300005
#define inf 12345678900000000
#define maxn 100005
struct Edge{int u,v,w,next;}e[maxm<<1];
struct qj{int ma,ma2;}q[maxn<<2];
struct Edge1
{
    int u,v,w;
    bool operator <(const Edge1 &x) const{return w<x.w;}//按照边权排序 
}edge[maxm];
int n,m,vis[maxm],ans=inf,head[maxn],cnt,fa[maxn],mtree;
il void add(int u,int v,int w)
{
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
}//前向星加边
namespace smallesttree
{
    il int find(int x)
    {
        while(x!=fa[x]) x=fa[x]=fa[fa[x]];
        return x;
    }//并查集找祖先 
    il void init()
    {   
        for(re int i=1;i<=n;i++) fa[i]=i; //预处理并查集 
        for(re int i=0;i<m;i++) edge[i].u=read(),edge[i].v=read(),edge[i].w=read();
    }
    il void kruskal()
    {
        init();
        sort(edge,edge+m);
        re int T=0;
        for(re int i=0;i<m;++i)
        {
            re int eu=find(edge[i].u),ev=find(edge[i].v);//寻找祖先
            if(eu!=ev)
            {
                add(edge[i].u,edge[i].v,edge[i].w),add(edge[i].v,edge[i].u,edge[i].w);
                mtree+=edge[i].w;//记录子树大小 
                fa[ev]=eu;//合并 
                vis[i]=1;//标记该边为树边 
                if(++T==n-1) break;//边数等于节点数+1即为一颗树 
            }
        }
    } 
}
//求出最小生成树
namespace treecut
{
    int dep[maxn],father[maxn],top[maxn],W[maxn],a[maxn],size[maxn],son[maxn],seg[maxn],col;
    //dep:深度 father:父亲节点 top:重链的顶端 W:到根节点的距离 a:点的权值 size:子树大小 son:重儿子 seg:在线段树中的序号(dfs序) 
    il void dfs1(int u,int fr)
    {
        dep[u]=dep[fr]+1;
        size[u]=1;
        father[u]=fr;
        for(re int i=head[u];i;i=e[i].next)
        {
            re int v=e[i].v;
            if(v!=fr)
            {
                W[v]=W[u]+e[i].w;//W为每一个点到根节点的距离 
                dfs1(v,u);
                size[u]+=size[v];
                if(size[v]>size[son[u]]) son[u]=v;
            }
        }
    }//预处理出dep、size、father以及son 
    il void dfs2(int now,int fi)
    {
        top[now]=fi;
        seg[now]=++col;
        a[col]=W[now]-W[father[now]];//a为点的权值(它与之父亲节点边的权值)(相当于前缀和) 
        if(!son[now]) return;
        dfs2(son[now],fi);
        for(re int i=head[now];i;i=e[i].next)
        {
            re int v=e[i].v;
            if(v!=son[now]&&v!=father[now]) dfs2(v,v);
        }
    }//预处理出每个节点的top、seg以及权值 
    //树剖模板就不解释了 
    #define ls k<<1
    #define rs k<<1|1
    il bool CMP(int a,int b){return a>b;}
    il int getse(int x,int g,int z,int c)
    {
        re int a[5]={x,g,z,c};
        sort(a,a+4,CMP);
        for(re int i=1;i<3;++i)
        {
            if(a[i]!=a[0]) return a[i];
        }
    }//找到两个区间的最大值和严格次大值(四个数)的最大值与严格次大值 
    // 就是合并两个区间的最大值和严格次大值
    il void build(int k,int l,int r)
    {
        if(l==r)
        {
            q[k].ma=a[l];
            return;
        }
        re int mid=(l+r)>>1;
        build(ls,l,mid),build(rs,mid+1,r);
        q[k].ma=max(q[ls].ma,q[rs].ma);
        q[k].ma2=getse(q[ls].ma,q[rs].ma,q[ls].ma2,q[rs].ma2);
    }//预处理出区间最大值与次大值 
    il qj query(int k,int l,int r,int ll,int rr)
    {
        if(ll>r||rr<l) return (qj){-inf,-inf};
        if(ll<=l&&rr>=r) return (qj){q[k].ma,q[k].ma2};
        re int mid=(l+r)>>1;
        re qj t1=query(ls,l,mid,ll,rr),t2=query(rs,mid+1,r,ll,rr);
        return (qj){max(t1.ma,t2.ma),getse(t1.ma,t2.ma,t1.ma2,t2.ma2)};
    }//查询区间的区间的最大值与次小值 
    il int LCA(int u,int v,int d)
    {
        re int need=-inf;
        while(top[u]!=top[v])
        {
            if(dep[top[u]]<dep[top[v]]) swap(u,v);
            qj temp=query(1,1,n,seg[top[u]],seg[u]);
            u=father[top[u]];
            need=max(need,(temp.ma==d)?temp.ma2:temp.ma);//严格次小边(如果temp.ma==k就是非严格次小) 
        }
        if(dep[u]<dep[v]) swap(u,v);//找到LCA 
        qj temp=query(1,1,n,seg[v]+1,seg[u]);
        return max(need,(temp.ma==d)?temp.ma2:temp.ma);//同上 
    }
    il void init()
    {
        dfs1(1,0),dfs2(1,1),build(1,1,n);
    }
}
//树链剖分 
signed main()
{
    n=read(),m=read();
    smallesttree::kruskal();//求出最小生成树
    treecut::init();//预处理
    for(re int i=0;i<m;++i)
    {
        if(vis[i]) continue;//枚举所有非树边(没有在最小生成树的边)
        re int temp=mtree/*最小生成树边权和*/+edge[i].w/*本来的树边的边权*/-treecut::LCA(edge[i].u,edge[i].v,edge[i].w)/*找到严格次小边的边权*/;
        if(ans>temp&&temp!=mtree+e[i].w/*其实就是严格此小边不为0(没有找到严格次小边)*/&&temp>mtree) ans=temp;
    }
    printf("%lld",ans);
    return 0;
}

代码 LCT实现

学习了LCT后,发现这道题貌似可以LCT做,而且代码短了很多

先跑一边 K r u s k a l Kruskal Kruskal,并在LCT上跑出最小生成树的样子

然后对于每一条非树边,加入后会产生一个环,我们删去环内最大值(若最大值和改变相同则插入次大值)即可

#include<bits/stdc++.h>
using namespace std;
#define il inline
#define re register
#define debug printf("Now is Line : %d\n",__LINE__)
#define file(a) freopen(#a".in","r",stdin);//freopen(#a".out","w",stdout)
#define inf 1234567890000000000
#define ll long long
il int read() {
    re int x = 0, f = 1; re char c = getchar();
    while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar();}
    while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
    return x * f;
}
#define rep(i, s, t) for(re int i = s; i <= t; ++ i)
#define get_fa(x) ch[1][fa[x]] == x
#define isroot(x) ch[1][fa[x]] == x || ch[0][fa[x]] == x
#define updown(x) tag[x] ^= 1, swap(ch[0][x], ch[1][x])
#define maxn 600005
struct edge {
    int u, v, w;
}e[maxn];
int n, m, is[maxn],ch[2][maxn], fa[maxn], tag[maxn], st[maxn], Fa[maxn];
int mx1[maxn], mx2[maxn], val[maxn];
ll ans, Ans = inf;
il int find(int x) {
    while(Fa[x] != x) x = Fa[x] = Fa[Fa[x]];
    return x;
}
il bool cmp(edge a, edge b) {return a.w < b.w;}
il void pushdown(int x) {
    if(!tag[x]) return;
    if(ch[0][x]) updown(ch[0][x]);
    if(ch[1][x]) updown(ch[1][x]);
    tag[x] = 0;
}
il void pushup(int x) {
    mx1[x] = val[x];
    if(mx1[x] < mx1[ch[0][x]]) mx2[x] = mx1[x], mx1[x] = mx1[ch[0][x]];
    else if(mx1[x] > mx1[ch[0][x]]) mx2[x] = max(mx2[x], mx1[ch[0][x]]);
    if(mx1[x] < mx1[ch[1][x]]) mx2[x] = mx1[x], mx1[x] = mx1[ch[1][x]];
    else if(mx1[x] > mx1[ch[1][x]]) mx2[x] = max(mx2[x], mx1[ch[1][x]]);
    mx2[x] = max(max(mx2[x], mx2[ch[1][x]]), mx2[ch[0][x]]);
}
il void rotate(int x) {
    int y = fa[x], z = fa[y], w = get_fa(x), k = get_fa(y);
    if(isroot(y)) ch[k][z] = x; fa[x] = z;
    ch[w][y] = ch[w ^ 1][x], fa[ch[w ^ 1][x]] = y;
    ch[w ^ 1][x] = y, fa[y] = x;
    pushup(y), pushup(x);
}
il void Splay(int x) {
    int top = 0, y = x;
    st[++ top] = y;
    while(isroot(y)) st[++ top] = y = fa[y];
    while(top) pushdown(st[top --]);
    while(isroot(x)) {
        if(isroot(fa[x])) rotate(get_fa(x) == get_fa(fa[x]) ? fa[x] : x);
        rotate(x);
    }
}
il void access(int x) {for(re int y = 0; x; x = fa[y = x]) Splay(x), ch[1][x] = y, pushup(x);}
il void makeroot(int x) {access(x), Splay(x), updown(x);}
il int findroot(int x) {
    access(x), Splay(x);
    while(ch[0][x]) x = ch[0][x];
    return Splay(x), x;
}
il void spilt(int x, int y) {makeroot(x), access(y), Splay(y);}
il void link(int x, int y) {
    makeroot(x);
    if(findroot(y) != x) fa[x] = y;
}
int main() {
    n = read(), m = read();
    rep(i, 1, m) e[i].u = read(), e[i].v = read(), e[i].w = read();
    rep(i, 1, n) Fa[i] = i;
    sort(e + 1, e + 1 + m, cmp);
    rep(i, 1, m) {
        val[i + n] = e[i].w;
        int u = e[i].u, v = e[i].v, a = find(u), b = find(v);
        if(a != b) ans += e[i].w, link(u, i + n), link(i + n, v), is[i] = 1, Fa[a] = b;
    }
    rep(i, 1, m) {
        if(is[i]) continue;
        int u = e[i].u, v = e[i].v;
        spilt(u, v);
        if(e[i].w > mx1[v]) Ans = min(Ans, (ll)e[i].w - mx1[v]);
        else Ans = min(Ans, (ll)e[i].w - mx2[v]);
    }
    printf("%lld", Ans + ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用 ArcGIS 中的 Network Analyst 扩展工具来构建最小生成树网络。首先,需要将空间数据导入 ArcGIS 中并建立网络数据集。然后,使用 Network Analyst 中的最小生成树工具来分析网络并生成最小生成树网络。可以使用可视化工具来查看和分析结果。 ### 回答2: ArcGIS是一个功能强大的地理信息系统软件,可以用于构建和分析网络。如果想使用ArcGIS构建最小生成树网络,可以按照以下步骤进行操作: 1. 收集和准备数据:首先需要收集相关的地理数据,包括道路、管道或电缆等网络要素。确保数据的正确性和完整性,并按照ArcGIS要求的格式进行导入和准备。 2. 创建网络数据集:在ArcGIS中,可以使用“网络分析工具箱”创建网络数据集。通过这些工具,可以将道路、管道或电缆等要素连接在一起,形成一个完整的网络。 3. 创建最小生成树:在网络数据集中,可以使用“最小生成树”分析工具来创建最小生成树。该工具将基于网络数据集中的边的成本或权重,计算出连接所有节点的最小生成树。 4. 分析和优化:生成最小生成树后,可以使用ArcGIS的其他分析工具来评估和优化网络。例如,可以使用网络分析工具箱中的“最短路径”工具来找到最短路径,或使用“网络中心性”工具来识别网络中的关键节点。 5. 可视化和输出结果:最后,可以使用ArcGIS的地图制作工具将最小生成树网络可视化,并输出为地图或其他格式的文件,使其便于与他人共享或进一步分析。 需要注意的是,在使用ArcGIS进行网络分析时,需要确保数据的准确性和一致性,并根据实际需求选择合适的算法和参数进行分析。同时,还需要掌握一定的ArcGIS使用技巧和网络数据分析知识,以便能够充分利用ArcGIS的功能进行最小生成树网络的构建和分析。 ### 回答3: 使用ArcGIS构建最小生成树网络是一个相对简单的过程,可以通过以下步骤实现: 1.准备数据:首先,你需要准备一份包含节点和边的空间数据。节点表示网络的各个位置,可以是点、线或面;边表示这些位置之间的连接关系,可以是线或面。确保数据的几何信息正确,并且每个要素都有唯一的标识符。 2.导入数据:在ArcGIS界面中,选择合适的工具箱并打开。将准备好的数据导入到ArcGIS中,可以使用"导入要素类"工具或直接拖放数据到地图窗口。确保数据导入后在图层列表中可见。 3.创建网络:在工具箱中选择"网络数据集"工具并打开。设置网络数据集的名称、位置和坐标系等属性,并指定导入的节点和边要素类作为网络的基本要素。点击生成按钮开始创建网络数据集,这可能需要一些时间,具体取决于数据的大小。 4.编辑网络:一旦网络数据集创建完成,就可以进行网络的编辑。选择"网络编辑"工具并打开编辑工具栏。在编辑工具栏中选择合适的编辑工具,如添加节点、添加边或删除要素等,然后在地图窗口中进行相应的操作。完成编辑后,保存并退出编辑模式。 5.生成最小生成树:选择"网络分析"工具箱并打开,然后选择"最小生成树"工具。指定网络数据集和生成路径的起始节点,并设置生成树的规则和约束等参数。点击运行按钮开始生成最小生成树,结果将以图层的形式显示在地图窗口中。 6.分析和应用:一旦最小生成树生成完成,可以进行进一步的分析和应用。可以计算最小生成树的拓扑性质,如总长度、平均长度等,也可以将最小生成树用于路径规划、资源分配等决策问题。 综上所述,使用ArcGIS构建最小生成树网络需要准备数据、导入数据、创建网络、编辑网络、生成最小生成树以及分析和应用等步骤。通过这些操作,可以在ArcGIS中构建和分析最小生成树网络,为地理空间分析提供支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值