洛谷.4180.[模板]次小生成树Tree(Kruskal LCA 倍增)

构建完MST后,枚举非树边(u,v,w),在树上u->v的路径中找一条权值最大的边(权为maxn),替换掉它
这样在 w=maxn 时显然不能满足严格次小。但是这个w可以替换掉树上严格小于maxn的次大边
用倍增维护MST上路径的最大值、次大值,每条非树边的查询复杂度就为O(logn)

ps:1.倍增更新次大值时,未必是从最大值转移,要先赋值较大的次大值,再与较小的那个最大值比较。
2.maxn!=w时,是可以从maxn更新的(不能更新就是上面情况啊)
倍增处理部分我还是在dfs里写吧 md改了一晚上

//648ms 31.61MB
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
typedef long long LL;
const int N=1e5+5,M=3e5+5,D=18,MAXIN=2e6;

int n,m,F[N],Enum,H[N],to[N<<1],nxt[N<<1],val[N<<1],dep[N],fa[N][D],maxn[N][D],s_maxn[N][D];
bool ist[M];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Edge
{
    int fr,to,val;
    bool operator <(const Edge &a)const{
        return val<a.val;
    }
}e[M];

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-‘0‘,c=gc());
    return now;
}
inline void AddEdge_T(int u,int v,int w){
    to[++Enum]=v, nxt[Enum]=H[u], val[Enum]=w, H[u]=Enum;
    to[++Enum]=u, nxt[Enum]=H[v], val[Enum]=w, H[v]=Enum;
}
int Find(int x){
    return x==F[x]?x:F[x]=Find(F[x]);
}
LL Kruskal()
{
    LL res=0ll;
    std::sort(e+1,e+1+m);
    for(int i=1; i<=n; ++i) F[i]=i;
    for(int u,v,k=0,i=1; i<=m; ++i)
    {
        u=Find(e[i].fr), v=Find(e[i].to);
        if(u!=v)
        {
            ist[i]=1, F[u]=v, res+=e[i].val;
            AddEdge_T(e[i].fr,e[i].to,e[i].val);//别拿u,v建边
            if(++k==n-1) break;
        }
    }
    return res;
}
void pre_DFS(int x)
{
    for(int i=1; i<D && (1<<i)<=dep[x]; ++i)
    {
        fa[x][i]=fa[fa[x][i-1]][i-1];
        maxn[x][i]=std::max(maxn[fa[x][i-1]][i-1],maxn[x][i-1]);
        s_maxn[x][i]=std::max(s_maxn[fa[x][i-1]][i-1],s_maxn[x][i-1]);
        if(maxn[fa[x][i-1]][i-1]>maxn[x][i-1])
            s_maxn[x][i]=std::max(s_maxn[x][i],maxn[x][i-1]);
        else if(maxn[fa[x][i-1]][i-1]<maxn[x][i-1])//不要直接else
            s_maxn[x][i]=std::max(s_maxn[x][i],maxn[fa[x][i-1]][i-1]);
    }
    for(int v,i=H[x]; i; i=nxt[i])
        if((v=to[i])!=fa[x][0])
            fa[v][0]=x, dep[v]=dep[x]+1, maxn[v][0]=val[i], pre_DFS(v);
}
//void Init()//这步先处理i!
//{
//  for(int i=1; i<D; ++i)
//      for(int x=2; x<=n; ++x)
//      {
//          fa[x][i]=fa[fa[x][i-1]][i-1];
//          maxn[x][i]=std::max(maxn[fa[x][i-1]][i-1],maxn[x][i-1]);
//          s_maxn[x][i]=std::max(s_maxn[fa[x][i-1]][i-1],s_maxn[x][i-1]);
//          if(maxn[fa[x][i-1]][i-1]>maxn[x][i-1])
//              s_maxn[x][i]=std::max(s_maxn[x][i],maxn[x][i-1]);
//          else if(maxn[fa[x][i-1]][i-1]<maxn[x][i-1])//不要直接else
//              s_maxn[x][i]=std::max(s_maxn[x][i],maxn[fa[x][i-1]][i-1]);
//      }
//}
int Query(int u,int lca,int w)
{
    int res=0;
    for(int i=D-1; i>=0; --i)
        if(dep[fa[u][i]]>=dep[lca])
        {
            if(maxn[u][i]!=w) res=std::max(res,maxn[u][i]);
            else res=std::max(res,s_maxn[u][i]);
            u=fa[u][i];
        }
    return res;
}
int LCA(int u,int v)
{
    if(dep[u]<dep[v]) std::swap(u,v);
    int t=dep[u]-dep[v];
    for(int i=D-1; i>=0; --i)
        if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
//      if(t&(1<<i)) u=fa[u][i];
    if(u==v) return u;
    for(int i=D-1; i>=0; --i)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i], v=fa[v][i];
    return fa[u][0];
}

int main()
{
    n=read(),m=read();
    for(int i=1; i<=m; ++i) e[i].fr=read(),e[i].to=read(),e[i].val=read();
    LL tot=Kruskal(),res=e[m].val+tot;
    dep[1]=1, pre_DFS(1);//, Init();
    for(int u,v,w,lmax,rmax,i=1; i<=m; ++i)
        if(!ist[i])
        {
            u=e[i].fr, v=e[i].to, w=LCA(u,v);
            lmax=Query(u,w,e[i].val), rmax=Query(v,w,e[i].val);
            res=std::min(res,tot+e[i].val-std::max(lmax,rmax));
        }
    printf("%lld",res);

    return 0;
}

 

原文地址:https://www.cnblogs.com/qb666/p/8486266.html

时间: 2024-11-08 22:00:46

洛谷.4180.[模板]次小生成树Tree(Kruskal LCA 倍增)的相关文章

bzoj 1977 洛谷P4180 严格次小生成树

Description: 给定一张N个节点M条边的无向图,求该图的严格次小生成树.设最小生成树边权之和为sum,那么严格次小生成树就是边权之和大于sum的最小的一个 Input: 第一行包含两个整数N 和M,表示无向图的点数与边数. 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z. Output: 包含一行,仅一个数,表示严格次小生成树的边权和.(数据保证必定存在严格次小生成树) 思路:先求出原图的最小生成树,然后继续从小到大枚举边(x,y),对于x,y用倍

P4180 严格次小生成树[BJWC2010] Kruskal,倍增

题目链接\(Click\) \(Here\). 题意就是要求一个图的严格次小生成树.以前被题面吓到了没敢做,写了一下发现并不难. 既然要考虑次小我们就先考虑最小.可以感性理解到一定有一种次小生成树,可以由最小生成树删一条边再加一条边得到.我们枚举加上去的这一条边,加上去以后原\(mst\)会成为一个基环树,想让它次小就在这个环里找一条最长的边(不包含新加进去的)删掉就好.放在树上来讲,就是找到\(u\)到\(v\)路径上的最大值.这样我们就有了非严格次小生成树. 严格要怎么处理?我们需要排除新加

【BZOJ 1977】 [BeiJing2010组队]次小生成树 Tree

1977: [BeiJing2010组队]次小生成树 Tree Time Limit: 10 Sec  Memory Limit: 512 MB Submit: 2313  Solved: 544 [Submit][Status][Discuss] Description 小 C 最近学了很多最小生成树的算法,Prim 算法.Kurskal 算法.消圈算法等等. 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了.小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格

1977: [BeiJing2010组队]次小生成树 Tree

题解:和cf的一道题比较类似 首先跑一个MST 对于这个树做树链剖分 枚举不在这个树上的边找严格小于这条边的最大边权值 然后求ans #include <bits/stdc++.h> #define ll long long const int MAXN=1e5+10; const int maxn=3e5+10; const int inf=1e9+20; using namespace std; ll read(){ ll x=0,f=1;char ch=getchar(); while(

洛谷P4180 [Beijing2010组队]次小生成树Tree(最小生成树,LCT,主席树,倍增LCA,倍增,树链剖分)

洛谷题目传送门 %%%天平巨佬和山楠巨佬%%% 他们的题解 思路分析 具体思路都在两位巨佬的题解中.这题做法挺多的,我就不对每个都详细讲了,泛泛而谈吧. 首先kruskal把最小生成树弄出来,因为要求次小生成树.至于为什么次小一定只在最小的基础上改变了一条边,我也不会证......打表找规律大法好 剩下的可以有一堆数据结构来维护最大值和次大值(原理两位巨佬都讲清楚了,这里只分析一下算法的优劣) 倍增+LCA 山楠巨佬的做法,我也写了这一种.复杂度\(O(MlogM(kruscal)+MlogN(

洛谷P4180 [Beijing2010组队]次小生成树Tree

题目描述 小C最近学了很多最小生成树的算法,Prim算法.Kurskal算法.消圈算法等等.正当小C洋洋得意之时,小P又来泼小C冷水了.小P说,让小C求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是EM,严格次小生成树选择的边集是ES,那么需要满足:(value(e)表示边e的权值) \sum_{e \in E_M}value(e)<\sum_{e \in E_S}value(e)∑e∈EM??value(e)<∑e∈ES??value(e)

[BJOI 2010]次小生成树Tree

Description 小 C 最近学了很多最小生成树的算法,Prim 算法.Kurskal 算法.消圈算法等等. 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了.小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 e的权值)  这下小 C 蒙了,他找到了你,希望你帮他解决这个问题. Input 第一行包含两个整数N 和M,表示无向图

【C++】最近公共祖先LCA(Tarjan离线算法)&amp;&amp; 洛谷P3379LCA模板

1.前言 首先我们介绍的算法是LCA问题中的离线算法-Tarjan算法,该算法采用DFS+并查集,再看此算法之前首先你得知道并查集(尽管我相信你如果知道这个的话肯定是知道并查集的),Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解(个人认为). 2.思想 下面详细介绍一下Tarjan算法的思想: 1.任选一个点为根节点,从根节点开始. 2.遍历该点u所有子节点v,并标记这些子节点v已被访问过. 3.若是v还有子节点,返回2,否则下一步. 4.合并v到u上. 5.寻找与当前点

AC自动机(附洛谷P3769模板题)

首先,介绍一下AC自动机(Aho-Corasick automaton),是一种在一个文本串中寻找每一个已给出的模式串的高效算法. 在学习AC自动机之前,你需要先学习Trie树和KMP算法,因为AC自动机正式利用并结合了两者的思想. 说到实际的不同,其实AC自动机只是在Trie树上引入了一个类似KMP中next数组的东西叫做Fail指针. 对于每一个节点,Fail指针指向该节点所代表的字符串中,次长的.在Trie树中存在的后缀(因为最长的在Trie树种存在的后缀就是其本身)所代表的节点. 举例: