严格次小生成树 P4180

求出最小生成树,并以“树边”进行倍增,用于维护lca和两点间路径中的某条最大边和次小边,然后暴力枚举非树边,即可获得严格次小生成树,详细见代码

ps:在寻找非严格次小生成树时,也可以利用树上倍增的想法,从而将非严格次小生成树的复杂度变为O(mlogn),只需要将代码中的维护次小边数组去掉即可

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define mem(a, b) memset(a,b,sizeof(a))
#define INF 2147483647000000
#define DBG printf("this is a input\n")
#define fi first
#define se second
#define mk(a, b) make_pair(a,b)
#define p_queue priority_queue

ll gcd(ll a, ll b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll lcm(ll a, ll b) {
    return a / gcd(a, b) * b;
}
ll n , m, cnt;
ll used[300005], head[300005]; //used表示哪些边已用
ll fa[300005],mst_value = 0; //mst_value为最小生成树的大小
ll vis[300005] , deep[300005]; //vis用于bfs,deep用于存深度用于计算lca
ll maxn[300005][25], minn[300005][25], bz[300005][25]; //maxn[i][j]表示以i为节点向上走2^j次方个节点,这条路径上的最大边
//minn同maxn即为次小边。
//bz[i][j]用于存放以i出发向上2^j的节点是谁用于求lca
struct e
{
    ll u, t, w, next;
    bool operator < (const e& no) const{
        return w < no.w;
    }
}edge[300005],b_edge[600005];
void add(ll f, ll t, ll w)
{
    b_edge[cnt].w = w;
    b_edge[cnt].t = t;
    b_edge[cnt].next = head[f];
    head[f] = cnt ++;
}
struct node
{
    ll v , deep;
    node(ll a, ll b):v(a),deep(b){}
};
void Init()
{
    mem(head,-1);
    cnt = 0;
    for(int i = 1 ; i <= n ; i ++)
        fa[i] = i;
}
ll findroot(ll x)
{
    if(fa[x] == x)
        return x;
    return fa[x] = findroot(fa[x]);
}
bool merge(ll x ,ll y)
{
    ll fax = findroot(x);
    ll fay = findroot(y);
    if(fax != fay)
    {
        fa[fax] = fay;
        return true;
    }
    return false;
}
void kruskal()
{
    ll cnt = 0;
    for(ll i = 1 ; i <= m ; i ++)
    {
        if(cnt == n-1)
            break;
        ll v = edge[i].t , u = edge[i].u;
        if(merge(v,u))
        {
            cnt++;
            used[i] = 1; //标记用了哪些边
            add(edge[i].u,edge[i].t,edge[i].w); //将树边加入前向星,非树边不管
            add(edge[i].t,edge[i].u,edge[i].w);
            mst_value += edge[i].w;
        }
    }
}
void bfs(ll root , ll cur) // 广搜初始化maxn,minn以及deep
{
    minn[root][0] = - INF; //次小边数组初始化
    deep[root] = 0; //根节点深度初始化
    queue <node> q;
    q.push(node(root,cur));
    vis[root] = 1;
    while(!q.empty())
    {
        node no = q.front();
        q.pop();
        for(ll i = head[no.v] ; i != -1 ; i = b_edge[i].next)
        {
            ll v = b_edge[i].t;
            if(!vis[v])
            {
                vis[v] = 1;
                deep[v] = deep[no.v] + 1;
                bz[v][0] = no.v;
                maxn[v][0] = b_edge[i].w; // 以v出发向上2^0个节点,这条路径中,只有一条路即为这条边
                minn[v][0] = -INF;
                q.push(node(v,deep[v]));
            }

        }
    }
}
void Deal() //倍增处理
{
    for(ll i = 1 ; i <= 20 ; i ++)
    {
        for(ll j = 1 ; j <= n ; j ++)
        {
            //以j出发向上2^j次方的节点 = 以j+2^(j-1)出发向上2^(j-1)
            bz[j][i] = bz[bz[j][i-1]][i-1]; //处理bz数组,计算节点
            maxn[j][i] = max(maxn[j][i-1], maxn[bz[j][i-1]][i-1]);
            minn[j][i] = max(minn[j][i-1], minn[bz[j][i-1]][i-1]);
            //如果两个区间不同,则将较小的那条边给次小数组维护
            if(maxn[j][i-1] > maxn[bz[j][i-1]][i-1])
                minn[j][i]=max(minn[j][i], maxn[bz[j][i-1]][i-1]);
            else if(maxn[j][i-1] < maxn[bz[j][i-1]][i-1])
                minn[j][i]=max(minn[j][i], maxn[j][i-1]);
        }
    }
}
ll LCA(ll x ,ll y) //倍增处理LCA
{
    if(deep[x] < deep[y]) //先找出深度较深的点
        swap(x,y);
    for(int i = 20 ; i >= 0 ; i --) //将深度深的移动的和浅的相同
    {
        if(deep[bz[x][i]] >= deep[y])
            x = bz[x][i];
    }
    if (x == y) return x; //如果重合,说明该点就是LCA
    for(int i = 20 ; i >= 0 ; i --) //如果不重合,移动步数从大到小,如果移动后两个节点不一样,则更新
    {
        if(bz[x][i] ^ bz[y][i])
            x = bz[x][i] ,y = bz[y][i];
    }
    return bz[x][0];
}
ll path_maxn(ll u , ll v, ll w)
{
    ll ans = -INF;
    for(int i = 20 ; i >= 0 ; i --)
    {
        if(deep[bz[u][i]] >= deep[v]) //与LCA类似,用于寻找路径中的边的最大值
        {
            if(w != maxn[u][i])//如果即将加入的非树边和这条路径中(树边)的最大值不相同
                ans = max(ans,maxn[u][i]); //那么可以作为即将删除的边
            else
                ans = max(ans,minn[u][i]); //如果相同,则将这棵树中的次小值作为即将删除边
            u = bz[u][i];
        }
    }
    return ans;
}
int main(void)
{
    scanf("%lld %lld",&n ,&m);
    Init();
    for(ll i = 1 ; i <= m ; i ++)
        scanf("%lld %lld %lld", &edge[i].u, &edge[i].t, &edge[i].w);
    sort(edge+1,edge+1+m);
    kruskal();
    bfs(1,0);
    Deal();
    ll ans = INF;
    for(ll i = 1; i <= m ; i ++)
    {
        if(!used[i])
        {
            ll u = edge[i].u;
            ll v = edge[i].t;
            ll w = edge[i].w;
            ll lca = LCA(u, v);
            ll ans_ma = path_maxn(u, lca, w);
            ll ans_mi = path_maxn(v, lca, w);
            ans = min(ans, mst_value - max(ans_ma, ans_mi) + w);
        }

    }
    printf("%lld\n",ans);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值