【学习笔记】CODE FESTIVAL 2017 Final G. Tree MST

感觉是一道典中典题目!

首先发现一个性质,假设 ( x , y ) (x,y) (x,y)在最终的答案当中,那么对于 x → y x\to y xy这条链上的任意节点 z z z,应该满足 w z ≥ min ⁡ ( w x , w y ) w_z\ge \min(w_x,w_y) wzmin(wx,wy)

这样我们每次把权值最小的点取出来作为根,然后把所有可能有用的边找出来,最后跑 kruskal \text{kruskal} kruskal算法,但是复杂度可能不对!

我们发现这样一个类似于 点分树 的结构有非常好的性质,于是问题转化为求一个子树内 d i s x + w x dis_x+w_x disx+wx的最小值。

但是这样是有问题的。我们想一想一般的点分树能否实现上述功能?

发现这样是可以的。具体来说只要把所有点和 d i s x + w x dis_x+w_x disx+wx最小的那个点连起来就好了,因为两个点之间边的边权就是点权相加,因此所有点都只可能会和点权最小的那个点连边。

但是就是没想到\kk。以为跨分治中心的边不好处理所以就卡住了。。。

复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

remark \text{remark} remark 为什么这道题目可以用点分治解决?因为和路径相关!但是我以为最开始那个结论很重要。。。还是太菜了。。。

#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
#define db double
#define inf 0x3f3f3f3f
using namespace std;
const int N=2e5+5;
vector<pair<int,int>>G[N];
int n,rt,num,ps,sz[N],ban[N],fa[N];
ll w[N],dis[N],res;
vector<pair<ll,pair<int,int>>>edges;
void dfs(int u,int topf){
    sz[u]=1;
    for(auto v:G[u]){
        if(v.fi==topf||ban[v.fi])continue;
        dfs(v.fi,u),sz[u]+=sz[v.fi];
    }
}
void dfs2(int u,int topf){
    for(auto v:G[u]){
        if(v.fi==topf||ban[v.fi])continue;
        dfs2(v.fi,u);
    }
    if(rt==-1&&sz[u]>=(num+1)/2)rt=u;
}
void locate(int u,int topf){
    if(ps==-1||dis[u]+w[u]<dis[ps]+w[ps])ps=u;
    for(auto v:G[u]){
        if(v.fi==topf||ban[v.fi])continue;
        dis[v.fi]=dis[u]+v.se,locate(v.fi,u);
    }
}
void connect(int u,int topf){
    edges.pb(make_pair(dis[ps]+w[ps]+dis[u]+w[u],make_pair(ps,u)));
    for(auto v:G[u]){
        if(v.fi==topf||ban[v.fi])continue;
        connect(v.fi,u);
    }
}
void solve(int u){
    dfs(u,0),rt=-1,num=sz[u],dfs2(u,0);
    dis[rt]=0,ps=-1,locate(rt,0);
    connect(rt,0);ban[rt]=1;
    for(auto v:G[rt])if(!ban[v.fi])solve(v.fi);
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
    cin>>n;for(int i=1;i<=n;i++)cin>>w[i];
    for(int i=1;i<n;i++){
        int x,y,z;cin>>x>>y>>z;
        G[x].pb(make_pair(y,z));
        G[y].pb(make_pair(x,z));
    }
    solve(1);
    sort(edges.begin(),edges.end());
    for(int i=1;i<=n;i++)fa[i]=i;
    for(auto e:edges){
        ll z=e.fi;int x=e.se.fi,y=e.se.se;
        x=find(x),y=find(y);
        if(x!=y)fa[x]=y,res+=z;
    }
    cout<<res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值