• AcWing 356. 次小生成树


    分析

    这题做法很简单:

    • 跑一遍 \(\texttt{MST}\)(最小生成树),把这棵树建立起来,上面的边标记为树边。

    • 枚举非树边 \((u, v)\),记边权为 \(w\),考虑这条边能够提供的增量 \(del\)

      具体来说:只需要求出树上 \(u\to v\) 的路径上的边的最大值 \(mx_1\)严格次大值 \(mx_2\)(也就是保证 \(mx_2 < mx_1\)),假如 \(w>mx_1\),那我们就更新 \(del\to \min(del, w-mx_1)\),如果 \(w=mx_1\),就更新 \(del\to \min(del, w-mx_2)\).

    上面的思路简单来说就是枚举非树边,加入满足替换后增量最小的边以得到答案。

    实现

    关键在于使用什么样的数据结构维护树上路径的最大值与次大值。

    这里采用了线段树 + 树链剖分:

    线段树的每个节点维护了一个最大值与次大值,这两个信息都是可以合并的,\(\texttt{merge}\) 函数见下:

    Node merge(Node &L, Node &R){
    	int rec[]={L.mx1, L.mx2, R.mx1, R.mx2};
    	int mx1=-INF, mx2=-INF;
    	for(auto i: rec) mx1=max(mx1, i); 
    	rep(i,0,3) if(rec[i]!=mx1) mx2=max(mx2, rec[i]);
    	return {L.l, R.r, mx1, mx2};
    }
    

    然后我们可以求出树上 \(u\to v\) 路径上的 \(LCA\),记为 \(a\)

    那么只需要对路径 \(u\to a\) 以及 \(v\to a\) 分别询问一次得到两条树链的信息(最大值、次大值)再合并这两个信息即可。

    全部代码:

    // Problem: 次小生成树
    // Contest: AcWing
    // URL: https://www.acwing.com/problem/content/358/
    // Memory Limit: 512 MB
    // Time Limit: 1000 ms
    // 
    // Powered by CP Editor (https://cpeditor.org)
    
    #pragma GCC optimize("O3")
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<vector>
    using namespace std;
     
    #define debug(x) cerr << #x << ": " << (x) << endl
    #define rep(i,a,b) for(int i=(a);i<=(b);i++)
    #define dwn(i,a,b) for(int i=(a);i>=(b);i--)
    #define pb push_back
    #define all(x) (x).begin(), (x).end()
     
    #define x first
    #define y second
    using pii = pair<int, int>;
    using ll = long long;
     
    inline void read(int &x){
        int s=0; x=1;
        char ch=getchar();
        while(ch<'0' || ch>'9') {if(ch=='-')x=-1;ch=getchar();}
        while(ch>='0' && ch<='9') s=(s<<3)+(s<<1)+ch-'0',ch=getchar();
        x*=s;
    }
    
    const int N=1e5+5, M=1e6+5, INF=1e9+10;
    
    int n, m;
    
    struct Msg{
    	int u, v, w, vis;
    }buf[M];
    
    struct Edge{
    	int to, w, next;
    }e[M];
    
    int h[N], tot;
    
    void add(int u, int v, int w){
    	e[tot].to=v, e[tot].w=w, e[tot].next=h[u], h[u]=tot++;
    }
    
    int f[N];
    
    int find(int x){
    	return x==f[x]? x: f[x]=find(f[x]);
    }
    
    ll MST(){
    	ll res=0;
    	rep(i,1,n) f[i]=i;
    	rep(i,1,m){
    		int u=buf[i].u, v=buf[i].v, w=buf[i].w;
    		int pu=find(u), pv=find(v);
    		if(pu!=pv) f[pu]=pv, res+=w, add(u, v, w), add(v, u, w), buf[i].vis=true;
    	}
    	return res;
    }
    
    int fa[N], son[N], sz[N], dep[N];
    int W[N], val[N];
    int top[N], dfn[N], ts;
    
    void dfs1(int u, int f, int d){
    	fa[u]=f, sz[u]=1, dep[u]=d;
    	for(int i=h[u]; ~i; i=e[i].next){
    		int go=e[i].to;
    		if(go==f) continue;
    		W[go]=e[i].w;
    		dfs1(go, u, d+1);
    		sz[u]+=sz[go];
    		if(sz[son[u]]<sz[go]) son[u]=go;
    	}
    }
    
    void dfs2(int u, int t){
    	top[u]=t, dfn[u]=++ts, val[ts]=W[u];
    	if(!son[u]) return;
    	dfs2(son[u], t);
    	for(int i=h[u]; ~i; i=e[i].next){
    		int go=e[i].to;
    		if(go==fa[u] || go==son[u]) continue;
    		dfs2(go, go);
    	}
    }
    
    struct Node{
    	int l, r;
    	int mx1, mx2;
    	
    	#define ls u<<1
    	#define rs u<<1|1
    }tr[N<<2];
    
    Node merge(Node &L, Node &R){
    	int rec[]={L.mx1, L.mx2, R.mx1, R.mx2};
    	int mx1=-INF, mx2=-INF;
    	for(auto i: rec) mx1=max(mx1, i); 
    	rep(i,0,3) if(rec[i]!=mx1) mx2=max(mx2, rec[i]);
    	return {L.l, R.r, mx1, mx2};
    }
    
    void build(int u, int l, int r){
    	tr[u]={l, r};
    	if(l==r) return tr[u].mx1=val[l], tr[u].mx2=-INF, void();
    	int mid=l+r>>1;
    	build(ls, l, mid), build(rs, mid+1, r);
    	tr[u]=merge(tr[ls], tr[rs]);
    }
    
    void upd(int u, int x, int fir=-INF, int sec=-INF){
    	if(tr[u].l==tr[u].r){
    		tr[u].mx1=fir, tr[u].mx2=sec;
    		return;
    	}
    	int mid=tr[u].l+tr[u].r>>1;
    	if(x<=mid) upd(ls, x, fir, sec);
    	else upd(rs, x, fir, sec);
    	tr[u]=merge(tr[ls], tr[rs]);
    }
    
    Node query(int u, int l, int r){
    	if(l<=tr[u].l && tr[u].r<=r) return tr[u];
    	int mid=tr[u].l+tr[u].r>>1;
    	Node L={-1}, R={-1};
    	if(l<=mid) L=query(ls, l, r);
    	if(mid<r) R=query(rs, l, r);
    	if(L.l==-1) return R;
    	if(R.l==-1) return L;
    	return merge(L, R);
    }
    
    Node query(int u, int v){
    	Node res={-1}, tmp;
    	while(top[u]!=top[v]){
    		int t=top[u];
    		if(~res.l) res=merge((tmp=query(1, dfn[t], dfn[u])), res);
    		else res=query(1, dfn[t], dfn[u]);
    		u=fa[top[u]];
    	}
    	if(~res.l) res=merge((tmp=query(1, dfn[v], dfn[u])), res);
    	else res=query(1, dfn[v], dfn[u]);
    	return res;
    }
    
    int lca(int u, int v){
    	while(top[u]!=top[v]){
    		if(dep[top[u]]<dep[top[v]]) swap(u, v);
    		u=fa[top[u]];
    	}
    	return dep[u]<dep[v]? u: v;
    }
    
    signed main(){
    	memset(h, -1, sizeof h);	
    	cin>>n>>m;
    	rep(i,1,m){
    		int u, v, w; read(u), read(v), read(w);
    		buf[i]={u, v, w, 0};
    	}
    	
    	sort(buf+1, buf+1+m, [&](const Msg &a, const Msg &b){
    		return a.w<b.w;
    	});
    	
    	ll res=MST();
    
    	dfs1(1, -1, 1), dfs2(1, 1);
    	build(1, 1, n);
    	
    	int del=INF;
    	rep(i,1,m) if(!buf[i].vis){
    		int u=buf[i].u, v=buf[i].v, w=buf[i].w;
    		if(u==v) continue;
    		int a=lca(u, v);
    		Node L={-1}, R={-1}, res, rec;
    		
    		rec=query(a, a);
    		upd(1, dfn[a]);
    		if(a!=u) L=query(u, a);
    		if(a!=v) R=query(v, a);
    		upd(1, dfn[a], rec.mx1, rec.mx2);
    		
    		if(L.l==-1) res=R;
    		else if(R.l==-1) res=L;
    		else res=merge(L, R);
    		
    		if(w==res.mx1) del=min(del, w-res.mx2);
    		else del=min(del, w-res.mx1);
    	}
    	cout<<res+del<<endl;
    	
    	return 0;
    }
    
  • 相关阅读:
    AtCoder Grand Contest 001F Wide Swap
    生成函数/母函数入门学习
    树的点分治入门小结
    树链剖分入门小结
    有重复元素的全排列
    二项式界
    二项系数
    排列问题、组合问题
    容斥原理
    P3372 【模板】线段树 1
  • 原文地址:https://www.cnblogs.com/Tenshi/p/16463956.html
Copyright © 2020-2023  润新知