[算法学习笔记] 树链剖分(重链剖分)

概述

树链剖分能将一棵树剖分成若干条链的形式。从而能在树上用一些线性数据结构如线段树,树状数组等维护。树链剖分分为重链剖分,长链剖分,LCT 剖分等。最常用的是重链剖分。本文将讲解重链剖分。

本文将持续更新。

我们接下来给出一些定义。

  • 重儿子:指一个节点所有儿子中,子树最大的儿子。

  • 轻儿子:指一个节点除了重儿子其他的儿子。

  • 重边:连接一个节点和它重儿子的边。

  • 轻边:连接一个节点和它轻儿子的边。

image

(图源 https://blog.csdn.net/qq_41418281/article/details/108220247 ,侵删。)

在上图中,红色的节点即为重儿子,红色的边为重链。黄色的点和黄色的边分别对应其轻儿子和轻链。

实现

实现重链剖分,我们需要两边 dfs。第一遍 dfs 预处理出重儿子,每个节点的父节点(便于后面操作),子树大小(重儿子),节点深度(便于后面操作)。

第二遍 dfs,我们找到每条重链的链头(top)

这里给出参考代码。

重链剖分
void dfs1(int p, int d)
{
    int size = 1, ma = 0;
    depth[p] = d;
    for (auto q : Edge[p])
        if (!depth[q])
        {
            dfs1(q, d + 1);
            f[q] = p;
            size += siz[q];
            if (siz[q] > ma)
                son[p] = q, ma = siz[q];
        }
    siz[p] = size;
}
void dfs2(int p)
{
//    cout<<u<<"qwq"<<endl;;
    for(auto q:Edge[p])
    {
        if(!top[q]) 
        {
            if(q == son[p]) 
                top[q] = top[p];
            else 
                top[q] = q;
            dfs2(q);
        }
    }
}

性质

通过上图,不难发现,重链是一条以轻儿子为链头的一条链(根节点除外),而轻链是连接重链与重链之间的桥梁。每个重链的链头的父亲一定在另外一条重链上。因此,我们记录每个节点的父亲,每条链的链头,就能快速实现重链与重链之间的跳跃。

其次,每个节点最多只能位于一条重链上。且一棵节点数量为 \(n\) 的树,从树上任意一条节点走到根节点,所经过的轻边数量不超过 \(\log n\) 条。

剖分完成后,我们便能在树上实现快速跳跃,重链剖分求解 LCA 是一种典型应用。下文将讲解。

重链剖分求 LCA

下文假设要求 \(a,b\) 的 LCA。

首先,若 \(a,b\) 位于同一条链上,则 LCA 即为深度较小的那个点。

若不在同一条链上。则 LCA 要么在链头深度较小的那条链上,要么就是两个链头的 LCA。绝不可能在链头深度较大的链上。这很显然,一条链只有到了链头才有轻边与其他重链连接。因此,我们不断让链头深度较大的往上跳到上一条链,直到到达同一条链为止。

具体实现见代码。

重链剖分求 LCA(省去重链剖分部分)
int lca(int a,int b)
{
    if(a > b) swap(a,b);
    while(top[a] != top[b])
    {
     //   cout<<a<<" "<<b<<endl;
        if(depth[top[a]] > depth[top[b]]) a = f[top[a]];
        else b = f[top[b]];
    }
   return (depth[a] > depth[b] ? b : a);
}

应用

树剖套线段树模板

树剖的精髓在于我们可以将一条链上信息用数据结构维护,上文提到从树上任意一点走到根节点,所经过的轻边数量不超过 \(\log n\) 条,很多情况下我们可以将一条重链当作整体,套用数据结构处理。例如可以用线段树,树状数组等维护链。

下面将讲解重链剖分套用线段树典型题。

Description

如题,已知一棵包含 \(N\) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 \(x\)\(y\) 结点最短路径上所有节点的值都加上 \(z\)

  • 2 x y,表示求树从 \(x\)\(y\) 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)

  • 4 x 表示求以 \(x\) 为根节点的子树内所有节点值之和

对于 \(100\%\) 的数据: \(1\le N \leq {10}^5\)\(1\le M \leq {10}^5\)\(1\le R\le N\)\(1\le P \le 2^{30}\)。所有输入的数均在 int 范围内。

显然,对于树上 \(\forall\{x,y\}\),它们之间的最短路径一定是 \((x-lca(x,y)-y)\), 我们显然可以树上差分。但是后面不好操作。

下文“链” 若无特殊说明,都指重链。

前文提到,在重链剖分求 LCA 时,我们用到了类似于 “跳链” 的操作。具体地,对于点对 \((a,b)\),我们确信 \(LCA(a,b)\) 要么是 \(a,b\) 所处重链链头的 LCA,要么 LCA 位于链头深度较小的链上。绝不可能位于链头深度较大的链上。因此,对于链头深度较大的链,我们完全可以直接跳到上面的链上去。不要忘记跳到同一条链上后还要把两点之间的点扔到线段树里处理。

因此,对于操作 1 和操作 2,我们在跳链求 LCA 的同时,把跳过的链扔到线段树里处理一下就好了。因为对于同一条链,它们的 dfs 序是连续的。可以直接处理。

在处理之前,我们可以将所有点的点权映射到一个新数组中,便于处理。

对于操作 3, 4。令 \(dfn_i\) 表示 点 \(i\) 的 dfs 序,\(size_i\) 表示点 \(i\) 的子树大小。比较显然的是,以点 \(i\) 为根节点的子树所对应的 dfs 序一定是 \(dfn_i~dfn_i+size_i-1\)。显然是连续的,直接扔到线段树处理即可。

上文描述非常简单,实现需要注意一些细节。

模板题代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1000010;
vector <int> Edge[N];
int depth[N],son[N],dfn[N],siz[N],top[N],f[N]; 
int n,m,r,p;
int mark[N];// seg tree
int tree[N];
int val[N];
int cnt = 0;
int a[N]; // new array
int rk[N];
void dfs1(int u,int fat)
{
    f[u] = fat;
    depth[u] = depth[fat] + 1;
    siz[u] = 1; // size
    int maxn = 0,maxx = 0; // heavy_son
    for(auto v:Edge[u])
    {
        if(v == fat) continue;
        dfs1(v,u); 
        if(siz[v] > maxn)  
        {
            maxn = siz[v];
            maxx = v;
        }
        siz[u] += siz[v];
    }
    son[u] = maxx;
}
void dfs2(int u,int t)
{
 //   cout<<u<<endl;
    top[u] = t;
    dfn[u] = ++ cnt; // dfs 序
    a[cnt] = val[u];
    rk[cnt] = u;
    if(!son[u]) return;
    dfs2(son[u],t);
  //  cout<< son[u] <<"qwq"<<endl;
    for(auto v:Edge[u])
    {
        if(v != son[u] && v != f[u]) 
        {
            dfs2(v,v);
        }
    }
}
//----------------------- seg tree --------------------------------
void pushdown(int p,int len)
{
    tree[p*2] += (len-len/2)*mark[p];
    tree[p*2+1] += (len/2)*mark[p];
    mark[p*2] += mark[p];
    mark[p*2+1] += mark[p];
    mark[p] = 0;
    //if(p*2+1 == 5) cout<<len<<"erqiwe"<<endl;
   // cout<<p*2+1<<" "<<tree[p*2+1]<<endl;
}
void build(int l,int r,int p)
{
    if(l == r) 
    {
        tree[p] = a[l];
    //  //  cout<<a[l]<<"aya"<<endl;
     // cout<<"tree["<<p<<"]="<<tree[p]<<endl;
        return;
    }
    int mid = (l+r) / 2;
    build(l,mid,p*2);
    build(mid+1,r,p*2+1);
    tree[p] = tree[p*2] + tree[p*2+1];
}
void modify(int l,int r,int pl,int pr,int p,int d)
{
    if(l > pr || r < pl) return;
    else if(l >= pl && r <= pr) 
    {
        tree[p] += (r-l+1)*d;
        mark[p] += d;
        return;
    } 
    else
    {
        int mid = (l+r) / 2;
        pushdown(p,r-l+1);
        modify(l,mid,pl,pr,p*2,d);
        modify(mid+1,r,pl,pr,p*2+1,d);
        tree[p] = tree[p*2] + tree[p*2+1];
    }
}
int query(int l,int r,int pl,int pr,int p)
{
    if(l > pr || r < pl) return 0;
    else if(l >= pl && r <= pr) return tree[p];
    else
    {
        int mid = (l+r) / 2;
        pushdown(p,r-l+1);
        return query(l,mid,pl,pr,p*2) + query(mid+1,r,pl,pr,p*2+1);
    }
}
//------------------------------------- seg tree -------------------------------------
void update_path(int x,int y,int z)
{
    while(top[x] != top[y]) 
    {
        if(depth[top[x]] > depth[top[y]])
        {
            modify(1,n,dfn[top[x]],dfn[x],1,z);
            x = f[top[x]];
        }
        else
        {
            modify(1,n,dfn[top[y]],dfn[y],1,z);
            y = f[top[y]];
        }
    }
    if(depth[x] > depth[y]) modify(1,n,dfn[y],dfn[x],1,z);
    else modify(1,n,dfn[x],dfn[y],1,z);
}
int query_path(int x,int y)
{
    int ans = 0;
    while(top[x] != top[y])
    {
        if(depth[top[x]] > depth[top[y]])
        {
            ans += query(1,n,dfn[top[x]],dfn[x],1);
            x = f[top[x]];
        }
        else
        {
            ans += query(1,n,dfn[top[y]],dfn[y],1);
            y = f[top[y]];
        }
    }
    if(depth[x] > depth[y]) ans += query(1,n,dfn[y],dfn[x],1);
    else ans += query(1,n,dfn[x],dfn[y],1);
    return ans;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>r>>p;
    for(int i=1;i<=n;i++) cin>>val[i];
    for(int i=1;i<n;i++) // build tree
    {
        int u,v;
        cin>>u>>v;
        Edge[u].push_back(v);
        Edge[v].push_back(u);
    }
    dfs1(r,0);
    top[r] = r; 
    dfs2(r,r);
 //   cout<<rk[5]<<endl;
    build(1,n,1);
    //cout<<tree[rk[5]]<<endl;
  //  cout<<dfn[2]<<endl;
  //  cout<<tree[5]<<endl;
    while(m--)
    {
      //  cout<<tree[5]<<"qwq"<<endl;
        int op,x,y,z;
        cin>>op>>x;
        if(op == 1)
        {
            cin>>y>>z;
            update_path(x,y,z);
        }   
        else if(op == 2)
        {
            cin>>y;
            cout<<query_path(x,y)%p<<endl;
        }
        else if(op == 3)
        {
            cin>>z;
            modify(1,n,dfn[x],dfn[x] + siz[x]-1 ,1,z);
      //      cout<<tree[5]<<"!!!"<<endl;
        }
        else
        {
            cout<<query(1,n,dfn[x],dfn[x]+siz[x]-1,1)%p<<endl;
        }
    }
    return 0;
}

习题

CF396C on changing tree

Description

有一棵有 \(n\) 个点的有根树,树的根编号为 \(1\). 一开始每个节点上面的值都为 \(0\)

现在有 \(q\) 个询问,每个询问有两种类型:

1 v x k 你需要给 \(v\) 节点的值增加 \(x\),如果 \(v\) 的子孙 \(u\),与 \(v\) 的距离为 \(i\),那么需要给节点 \(u\) 上的值增加 \((x - i \times k)\) 。两个点间的距离定义为两点之间的最短路。

2 v 输出节点 \(v\) 上的值。

对于每个询问,你需要输出答案 $\mod 10^9+7 $后的值。

\(1\le n,q \ge 3\times 10^5\)

乍看问题比较棘手,对于修改操作,虽然是子树修改,但是子树内的修改内容不一致。

注意到只有单点询问,且对于子树内的修改都是有规律性的,和到子树根节点的距离有关。这使我们想到一个经典算法:差分。

具体地,令序列 \(b\) 为点权。差分数组 \(a_i=b_i-b_{i-1}\)。每次操作我们只需要令 \(b_v+x,b_v-k(v\in [dfn_u+1,dfn_u+siz_u-1])\) (数组名同上文模板题讲解)。

对于单点查询,只需要前缀和扫一遍即可。树剖完我们可以直接跳链查询。查询 \(\sum \limits_{i=1}^vb_i\) 即可。

其他就是树剖和线段树模板。注意线段树不要开小了。

Code
// LUOGU_RID: 163719782
#include <bits/stdc++.h>
#define int long long
#define lx (x<<1)
#define rx (x<<1|1) 
using namespace std;
const int N = 3e5+5;
const int mod = 1000000007;
int n,q;
int fa[N];
int depth[N],siz[N],son[N],dfn[N],top[N];
int cnt = 0;
vector <int> Edge[N];
int tree[N<<2],mark[N<<2];
int upd(int x) 
{
    return (x%mod+mod)%mod;
}
void dfs1(int u,int f)
{
    fa[u] = f;
    depth[u] = depth[f] + 1;
    siz[u] = 1;
    int maxn = 0 ,maxx = 0;
    for(auto v:Edge[u])
    {
        if(v == f) continue;
        dfs1(v,u);
        if(siz[v] > maxn)
        {
            maxn = siz[v];
            maxx = v;
        }
        siz[u] += siz[v];
    }
    son[u] = maxx;
} 
void dfs2(int u,int tp)
{
    dfn[u] = ++ cnt;
    top[u] = tp;
    if(!son[u]) return;
    dfs2(son[u],tp);
    for(auto v:Edge[u])
    {
        if(v == fa[u] || top[v]) continue;
        dfs2(v,v);
    }
} 
// ------------------ seg tree ------------------
void pushdown(int x, int l, int r) {
	int mid((l + r) >> 1);
	if (mark[x]) {
		(mark[lx] += mark[x]) %= mod; (mark[rx] += mark[x]) %= mod;
		(tree[lx] += mark[x] * (mid - l + 1)) %= mod; (tree[rx] += mark[x] * (r - mid)) %= mod;
		mark[x] = 0;
	}
} 
void pushup(int p)
{
    tree[p] = tree[p*2] + tree[p*2+1];
}
void modify(int l,int r,int pl,int pr,int p,int d)
{
    if(pl > pr) swap(pl,pr);
    if(l > pr || r < pl) return;
    if(l >= pl && r <= pr)
    {
       (mark[p] += d) %= mod, (tree[p] += d * (r - l + 1)) %= mod;
        return;
    }
    int mid = (l+r) >> 1;
    pushdown(p,l,r);
    if(pl <= mid) modify(l,mid,pl,pr,p*2,d);
    if(mid < pr) modify(mid+1,r,pl,pr,p*2+1,d);
    pushup(p);
}
int query(int l,int r,int pl,int pr,int p)
{
    if(pl > pr) swap(pl,pr);
  //  if(l > pr || r < pl) return 0;
    if(l >= pl && r <= pr) return tree[p];
    int mid = (l+r) >> 1;
    pushdown(p,l,r);
    int ans = 0;
    if(pl <= mid) ans += query(l,mid,pl,pr,p*2);
    if(mid < pr) ans += query(mid+1,r,pl,pr,p*2+1);
    pushup(p);
    return ans;
}
// ------------------ seg tree ------------------
int query_path(int x, int y) {//求x--y链上的权值和
	int res(0);
	while (top[x] != top[y]) res += query(1,n,dfn[x], dfn[top[x]],1), x = fa[top[x]];
	return upd(res + query(1,n,dfn[x], dfn[y],1));
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=2;i<=n;i++)
    {
        cin>>fa[i];
        Edge[i].push_back(fa[i]);
        Edge[fa[i]].push_back(i);
    } 
    dfs1(1,-1);
    dfs2(1,1);
    cin>>q;
    while(q--)
    {
        int op,u,x,k;
        cin>>op>>u;
        if(op == 1)
        {
            cin>>x>>k;  modify(1,n,dfn[u],dfn[u],1,x);
            if(siz[u] > 1)
                modify(1,n,dfn[u]+1,dfn[u]+siz[u]-1,1,-k);
          
        }
        else 
        {
          //  int ans = query_path(u,1);
            cout<< query_path(u,1)<<endl;
        }
    }
}

练习

  1. [COCI2011-2012#3] PLAĆE 树剖基本操作板子。
  2. [HAOI2015]树上操作 板子。

边权转点权

例题: [NOIP2013 提高组] 货车运输

Description

共有 \(n\) 座城市,\(m\) 条道路。每条道路都有一个限重 \(w_i\) 表示通过该边的车辆重量不得超过 \(w_i\)。每辆车要从 \(u\) 开到 \(v\),求不超过限重的前提下每辆车最多运多重的货物。

Analysis

简化题意:求两点之间最大边权最小值。

建出 kruskal 最大重构树后求两点 LCA 即可。kruskal 重构树上两点 LCA 即为亮点最大边权最小值/最小边权最大值。且 经过不大于/不小于边权 \(w_i\) 的点能到达的点为 \(w_i\) 的儿子

我们也可以求出最大生成树后求树上两点路径上边权最小值。反证:若一条边不在最大生成树上,我们走最大生成树上的边一定更优不劣。这显然。用树链剖分即可轻松解决。

此时问题在于 题目给定边权,而树链剖分是对点权进行操作。我们最好将边权转换为我们熟悉的点权。

注意到树上每个儿子只有一个父亲,因此我们可定义点权 \(a_ii\)\(i\)\(fa_i\) 的边权。(\(fa_i\)\(i\) 的父亲。)

树剖完跳重链查询时,跳重链不受影响,当 \(x,y\) 在一条重链时,设 \(depth_x < depth_y\),我们要求 \(dfn_{x+1},dfn_y\) 的区间值。原因显然。

Code

Code
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
constexpr int N = 1000010;
constexpr int INF = 0x3f3f3f3f;
typedef pair<int,int> PAIR;
struct Node
{
    int u,v,w;
    bool operator<(const Node &a)const{
        return w > a.w;
    }
};
int val[N],fa[N],a[N]; // dsu
int fat[N],depth[N],son[N],siz[N],tp[N],dfn[N];// poufen
int tree[N]; 
int cnt;
int n,m;
int vis[N];
vector <PAIR> Edge[N];
vector <Node> qwq;
void init(){for(int i=1;i<=n;i++) fa[i] =  i;}
int find(int x)
{
    if(x == fa[x]) return x;
    fa[x] = find(fa[x]);
    return fa[x];
}
void kruskal() // MST (MAX)
{
    for(auto t:qwq)
    {
        int u = t.u,v = t.v,w = t.w;
        int i = find(u),j = find(v);
        if(i == j) continue;
        fa[i] = fa[j];
        Edge[u].push_back(PAIR(v,w));
        Edge[v].push_back(PAIR(u,w));
    }
}
void dfs1(int u,int f)
{
    vis[u] = 1;
    fat[u] = f;
    depth[u] = depth[f] + 1;
    siz[u] = 1;
    int maxn = 0,maxx = 0;
    for(auto t:Edge[u])
    {
        int v = t.x,w = t.y;
        if(v == f) continue;
        dfs1(v,u);
        siz[u] += siz[v];
        if(siz[v] > maxn)
        {
            maxn = siz[v];
            maxx = v;
        }
        val[v] = w;
    }
    son[u] = maxx;
}
void dfs2(int u,int topp)
{
    dfn[u] = ++cnt;
    tp[u] = topp;
    a[cnt] = val[u]; // hash
    if(!son[u]) return;
    dfs2(son[u],topp);
    for(auto t:Edge[u])
    {
        int v = t.x,w = t.y;
        if(v == fat[u] || v == son[u]) continue;
        dfs2(v,v);
    }
}
void build(int l,int r,int p)
{
    if(l == r) 
    {
        tree[p] = a[l];
        return;
    }
    int mid = (l+r) /2;
    build(l,mid,p*2);
    build(mid+1,r,p*2+1);
    tree[p] = min(tree[p*2],tree[p*2+1]);
}
int query(int l,int r,int pl,int pr,int p)
{
    if(l > pr || r < pl) return INF;
    if(l >= pl && r <= pr) return tree[p];
    int mid = (l+r) /2;
    return min(query(l,mid,pl,pr,p*2),query(mid+1,r,pl,pr,p*2+1));
}
int query_path(int u,int v)
{
    int minn = INF;
    while(tp[u] != tp[v])
    {
        if(depth[tp[u]] > depth[tp[v]])
        {
            minn = min(minn,query(1,n,dfn[tp[u]],dfn[u],1));
            u = fat[tp[u]];
        }
        else 
        {
            minn = min(minn,query(1,n,dfn[tp[v]],dfn[v],1));
            v = fat[tp[v]];
        }
    }
    if(u == v) return minn;
    if(depth[u] > depth[v]) swap(u,v);
   minn = min(minn,query(1,n,dfn[u]+1,dfn[v],1));
    return minn;
}
void solve()
{
    int q;
    cin>>q;
    while(q--)
    {
        int u,v;
        cin>>u>>v;
        if(find(u) != find(v)) cout<<"-1"<<endl;
        else cout<<query_path(u,v)<<endl;
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    cin>>n>>m;
    memset(a,0x3f,sizeof(a));
    init();
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        qwq.push_back({u,v,w});
        qwq.push_back({v,u,w});
    }
    sort(qwq.begin(),qwq.end());
    kruskal();
    for(int i=1;i<=n;i++) 
    {
        if(!vis[i]) 
        {
            dfs1(i,0);
            dfs2(i,0);
        }
    }
    build(1,n,1);
    solve();
    return 0;
}

一些和树剖有关的题目

P4180 [BJWC2010] 严格次小生成树

Source

Description

求一棵 严格次小生成树。

结论:次小生成树和最小生成树只有一条边不同。

考虑 kruskal 求最小生成树过程。将所有带选边从小到大排序,若该边的两点不连通则加边。次小生成树是在最小生成树基础上 “放弃了”一条更优的边,选择了一条更劣的边,从而导致次小。显然,放弃多条更优的边不如放弃一条更优。

因此,我们首先求出最小生成树,记录哪些边是最小生成树上的边,称为树边,反之为非树边。

枚举添加哪条非树边,添加一条非树边意味着构成环,成了一个基环树。这也很套路,设当前非树边为 \((u,v)\),我们只需删除找MST 上点 \(u,v\) 之间边权最大的边,再添加该非树边即可。删除 MST 上更小的边显然不优。

用树剖即可。当然倍增 LCA 复杂度也是对的。

但是这么做有问题,我们要求 严格次小生成树,若 MST 上点 \((u,v)\) 间的最大边权 \(w_1=w\),(\(w\) 为当前枚举到的非树边边权。)就不是严格次小了。

这也很简单,树剖的时候同时维护一下边权次大值就好了。咋做呢?线段树每个节点维护当前区间边权最大和次大值。合并的时候最大值非常容易,对于次大值,将左儿子,右儿子的最大值,次大值这四个数排序就求出来了。

实现的时候挺麻烦的。

实现
#include <bits/stdc++.h>
#define int long long
#define x first
#define y second
using namespace std;
constexpr int N = 10000010;
constexpr int INF = 1e15;
typedef pair<int,int> PAIR;
struct Node
{
    int u,v,w,tag;
    bool operator <(const Node &a)const{
        return w < a.w;
    }
};
int n,m;
vector <PAIR> Edge[N];
vector <Node> qwq;
vector <Node> EDGE;
int fa[N];
int MST = 0;
int dfn[N],siz[N],depth[N],son[N],tp[N],fat[N],val[N],a[N];
int cnt = 0;
PAIR tree[N];
int ans = INF;
void init(){for(int i=1;i<=n;i++) fa[i] = i;}
int find(int x)
{
    if(x == fa[x]) return x;
    fa[x] = find(fa[x]);
    return fa[x];
}
void kruskal()
{
    int count = 0;
    for(auto &t:qwq)
    {
        int u = t.u,v = t.v,w = t.w;
        int fu = find(u),fv = find(v);
        if(fu == fv) 
        {
          //  cout<<"/qq"<<endl;
          //  t.tag = 1;
            continue;
        }
        t.tag = 1;
      //  cout<<u<<" "<<v<<endl;
        Edge[u].push_back(PAIR(v,w));
        Edge[v].push_back(PAIR(u,w));
        MST += w;
        fa[fu] = fv;
        if(++count == n-1) return;
    }
}
bool CMP(int a,int b){return a > b;}
inline int getse(int q,int w,int e,int r)
{
    int qaq[5] = {q,w,e,r};
    sort(qaq,qaq+4,CMP);
    for(int i = 1;i < 3;i++)
        if(qaq[i] != qaq[0]) return qaq[i];
    return 0;
}
void dfs1(int u,int f)
{
    depth[u] = depth[f] + 1;
    siz[u] = 1;
    fat[u] = f;
    int maxn = 0,maxx = 0;
   // cout<<Edge[u].size()<<endl;
    for(auto t:Edge[u])
    {
        int v = t.x,w = t.y;
      //  cout<<val[v]<<"qwq"<<endl;
        if(v == f) continue;
        val[v] = w;
      //  cout<<val[v]<<endl;
        dfs1(v,u);
        siz[u] += siz[v];
        if(siz[v] > maxn)
        {
            maxn = siz[v];
            maxx = v;
        }
    }
    son[u] = maxx;
}
void dfs2(int u,int topp)
{
    dfn[u] = ++cnt;
    a[cnt] = val[u];
    tp[u] = topp;
    if(!son[u]) return;
    dfs2(son[u],topp);
    for(auto t:Edge[u])
    {
        int v = t.x,w = t.y;
        if(v == fat[u] || v == son[u]) continue;
        dfs2(v,v);
    }
}
void build(int l,int r,int p)
{
    if(l == r) 
    {
        tree[p].x = a[l];
      //  cout<<tree[p]<<endl;
        return;
    }
    int mid = (l+r) >> 1;
    build(l,mid,p<<1);
    build(mid+1,r,p<<1|1);
    tree[p].x = max(tree[p<<1].x,tree[p<<1|1].x);
    tree[p].y = getse(tree[p<<1].x,tree[p<<1|1].x,tree[p<<1].y,tree[p<<1|1].y);
}
PAIR query(int l,int r,int pl,int pr,int p)
{
    if(l > pr || r < pl) return PAIR(-INF,-INF);
    if(l >= pl && r <= pr) return PAIR(tree[p].x,tree[p].y);
    int mid = (l+r) >> 1;
    PAIR t1 = query(l,mid,pl,pr,p<<1),t2 = query(mid+1,r,pl,pr,p<<1|1);
    return PAIR(max(t1.x,t2.x),getse(t1.x,t2.x,t1.y,t2.y));
}
int query_path(int a,int b,int d)
{
    int maxn = -INF;
    while(tp[a] != tp[b])
    {
        if(depth[tp[a]] > depth[tp[b]]) swap(a,b);
        PAIR tmp = query(1,n,dfn[tp[b]],dfn[b],1);
        b = fat[tp[b]];
        maxn = max(maxn,(tmp.x==d)?tmp.y:tmp.x);
    }
    if(depth[a] > depth[b]) swap(a,b);
    PAIR tmp = query(1,n,dfn[a]+1,dfn[b],1);
    return max(maxn,(tmp.x==d)?tmp.y:tmp.x);
}
void solve()
{
  //  cout<<EDGE.size()<<endl;
    for(auto t:EDGE)
    {
        int u = t.u,v = t.v,w = t.w;
        int maxn = query_path(u,v,w);
        if(maxn == w) continue;
       // cout<<maxn<<endl;
       int tmp = MST + w - maxn;
       if(ans > tmp && tmp != MST  + w && tmp > MST) ans = tmp;
     }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
   // memset(a,INF,sizeof(a));
 //   memset(val,INF,sizeof(val));
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        qwq.push_back({u,v,w,0});
    }
    sort(qwq.begin(),qwq.end());
    kruskal();
    dfs1(1,0);
    dfs2(1,0);
    build(1,n,1);
    for(auto t:qwq)
        if(!t.tag) EDGE.push_back(t);
    solve();
   //cout<<MST<<endl;
    cout<<ans<<endl;
    return 0;
}
posted @ 2024-06-30 12:15  SXqwq  阅读(73)  评论(0编辑  收藏  举报