【BJOI2010】次小生成树-最小生成树+倍增LCA

本文介绍了一种求解严格次小生成树的方法,利用最小生成树与倍增LCA技术,通过寻找非最小生成树边与最短路径中严格次大的边权,实现了O(MlogN)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意:给定一个带边权的无向图,求该图中严格次小生成树上的边权和。
做法:本题需要用到最小生成树+倍增LCA。
所谓严格次小生成树,就是边权和严格大于最小生成树的边权和最小的生成树。我们求非严格次小生成树时,是对于每一条不在最小生成树中的边,将它加入后,再从构成的环中去掉一个除了它之外最大的边,所有这样构成的树中边权和最小的就是次小生成树了。然而这次要求严格次小生成树,这就意味着它的边权和必须严格大于最小生成树,所以对于每条不在最小生成树中的边,如果它和它两个端点在最小生成树上路径中最大边相等,就要用严格次大的边来作为被删掉的边。树上两点间严格次大的边权可以用倍增LCA来找,具体的预处理方法这里解释不清,请看代码吧。这样一来,时间复杂度是O(MlogN)的,可以通过此题。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define ll long long
using namespace std;
int n,m;
ll totalsum,ans=2000000000;
int first[100010],tot=0,f[100010];
int fa[100010][21]={0},mx[100010][21]={0},smx[100010][21]={0},dep[100010]={0};
struct edge {int v,next;ll d;} e[200010];
struct forsort {int x,y;ll z;} a[300010];
bool chosen[300010]={0};

bool cmp(forsort a,forsort b)
{
    return a.z<b.z;
}

int find(int x)
{
    int r=x,i=x,j;
    while(f[r]!=r) r=f[r];
    while(i!=r)
    {
        j=f[i];
        f[i]=r;
        i=j;
    }
    return r;
}

void merge(int a,int b)
{
    int fa=find(a),fb=find(b);
    f[fa]=fb;
}

void insert(forsort a)
{
    e[++tot].v=a.y,e[tot].d=a.z,e[tot].next=first[a.x],first[a.x]=tot;
    e[++tot].v=a.x,e[tot].d=a.z,e[tot].next=first[a.y],first[a.y]=tot;
}

void kruskal()
{
    sort(a+1,a+m+1,cmp);
    int now=0;totalsum=0;
    for(int i=1;i<=n;i++) f[i]=i;
    for(int i=1;i<=m;i++)
    {
        if (now==n-1) break;
        int fx=find(a[i].x),fy=find(a[i].y);
        if (fx!=fy)
        {
            now++;
            totalsum+=a[i].z;
            chosen[i]=1;
            insert(a[i]);
            merge(a[i].x,a[i].y);
        }
    }
}

void dfs(int v)
{
    smx[v][0]=-1;
    for(int i=first[v];i;i=e[i].next)
        if (e[i].v!=fa[v][0])
        {
            fa[e[i].v][0]=v;
            mx[e[i].v][0]=e[i].d;
            dep[e[i].v]=dep[v]+1;
            dfs(e[i].v);
        }
}

void calc(ll &val1,ll val2,ll &val3,ll val4)
{
    ll v1,v2;
    v1=max(val1,val2);
    if (val3>=val2) v2=val3;
    else if (val4>=val1) v2=val4;
         else if (val1!=val2) v2=min(val1,val2);
              else v2=max(val3,val4);
    val1=v1,val3=v2;
}

ll query(ll d,int x,int y)
{
    ll mxx=0,smxx=0;
    if (dep[x]<dep[y]) swap(x,y);
    for(int i=20;i>=0;i--)
        if (dep[fa[x][i]]>=dep[y])
        {
            calc(mxx,mx[x][i],smxx,smx[x][i]);
            x=fa[x][i];
        }
    if (x!=y)
    {
        for(int i=20;i>=0;i--)
            if (fa[x][i]!=fa[y][i])
            {
                calc(mxx,mx[x][i],smxx,smx[x][i]);
                calc(mxx,mx[y][i],smxx,smx[y][i]);
                x=fa[x][i],y=fa[y][i];
            }
        calc(mxx,mx[x][0],smxx,smx[x][0]);
        calc(mxx,mx[y][0],smxx,smx[y][0]);
    }
    if (d==mxx&&smxx!=-1) return smxx;
    else return mxx;
}

void work()
{
    dfs(1);
    for(int i=1;i<=20;i++)
        for(int j=1;j<=n;j++)
        {
            fa[j][i]=fa[fa[j][i-1]][i-1];
            ll val1=mx[j][i-1],val2=mx[fa[j][i-1]][i-1],val3=smx[j][i-1],val4=smx[fa[j][i-1]][i-1];
            mx[j][i]=max(val1,val2);
            if (val3>=val2) smx[j][i]=val3;
            else if (val4>=val1) smx[j][i]=val4;
                 else if (val1!=val2) smx[j][i]=min(val1,val2);
                      else smx[j][i]=max(val3,val4);
        }
    for(int i=1;i<=m;i++)
        if (!chosen[i])
        {
            ll val=query(a[i].z,a[i].x,a[i].y);
            if (val!=-1) ans=min(ans,a[i].z-val);
        }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%lld",&a[i].x,&a[i].y,&a[i].z);

    kruskal();
    work();
    printf("%lld",totalsum+ans);

    return 0;
}
### BJOI2013 压力 题目解析 #### 问题描述 题目要求计算每个网络设备必须通过的数据包数量。给定一个无向图,其中存在 $ N $ 个节点和 $ M $ 条边,以及 $ Q $ 组询问,每组询问表示从某个源点到目标点之间的路径。需要统计哪些节点是这些路径中的必经之点。 此问题可以通过构建 **圆方树** 并利用其特性来解决[^1]。 --- #### 圆方树简介 圆方树是一种基于无向图的特殊结构,能够高效处理与割点和桥有关的问题。它由两类节点组成: - **圆形节点**:代表原图中的实际顶点。 - **方形节点**:对应于原图的一个双连通分量 (BCC),即一组不存在割点的顶点集合。 在该题中,我们需要关注的是如何标记并统计经过特定割点的路径数目[^4]。 --- #### 实现细节 以下是具体实现方法: 1. **构建圆方树** 使用 Tarjan 算法找到所有的割点及其对应的双连通分量,并以此为基础构造圆方树。对于每一个新发现的双连通分量,创建一个新的方形节点并与所属的割点相连。 2. **路径差分** 对于每次查询 $(u, v)$,将其转化为对圆方树上的一次简单路径操作。通过对路径上的所有割点执行加一的操作完成统计工作[^2]。 3. **线段树优化** 考虑到可能存在的大量修改请求,在最终阶段可以引入线段树或其他区间数据结构进一步加速更新过程。 下面给出一段伪代码展示上述逻辑的核心部分: ```python def tarjan(u, fa): dfn[u] = low[u] = time_stamp stk.append(u) for y in adj[u]: if not dfn[y]: tarjan(y, u) low[u] = min(low[u], low[y]) if low[y] >= dfn[u]: # Found articulation point or bridge build_bcc(u, y) # Build corresponding square node elif y != fa and dfn[y] < dfn[u]: low[u] = min(low[u], dfn[y]) def build_bcc(root, child): global poi r = ++poi while True: w = stk[-1] stk.pop() att(r, w) # Attach the vertex to current biconnected component if w == child: break att(r, root) # Query processing using tree difference technique on constructed round-square tree. for query in queries: path_diff(query.start, query.end) ``` --- #### 时间复杂度分析 整个算法的时间复杂度主要依赖以下几个方面: - 构造圆方树所需时间为 $ O(N + M) $。 - 每次查询涉及一次简单的路径遍历,总时间开销为 $ O(Q \log N) $ 当采用合适的数据结构辅助时。 因此总体效率较高,适合大规模输入场景下的应用需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值