Codeforces Round 949 E. Turtle and Intersected Segments 【扫描线、生成树】

E. Turtle and Intersected Segments

E

题意

n n n 个区间,第 i i i 个区间是 [ l i , r i ] [l_i, r_i] [li,ri],权值为 a i a_i ai
两个区间相交当且仅当: max ⁡ ( l 1 , l 2 ) ≤ min ⁡ ( r 1 , r 2 ) \max(l_1, l_2) \leq \min(r_1,r_2) max(l1,l2)min(r1,r2)
对于两两相交的区间,在它们之间连边,权值为: ∣ a i − a j ∣ |a_i - a_j| aiaj

最小生成树边权值之和为多少(或不存在)?

思路

首先,对于三条两两相交的线段 ( l 1 , r 1 , a 1 ) , ( l 2 , r 2 , a 2 ) , ( l 3 , r 3 , a 3 ) (l_1, r_1, a_1), (l_2, r_2, a_2), (l_3, r_3, a_3) (l1,r1,a1),(l2,r2,a2),(l3,r3,a3),不妨设 a 1 < a 2 < a 3 a_1 < a_2 < a_3 a1<a2<a3,不难发现:我们只需要保留 ( 1 , 2 ) , ( 2 , 3 ) (1, 2), (2, 3) (1,2),(2,3) 之间的边即可。
这是由于 a 3 − a 1 = a 3 − a 2 + a 2 − a 1 a_3 - a_1 = a_3 - a_2 + a_2 - a_1 a3a1=a3a2+a2a1,而在生成树中,原图中的每一个边权最大的那一条边一定不会出现在 M S T MST MST

因此我们可以用扫描线活动来解决这个流程,每条线段在 l l l 处加入,在 r + 1 r + 1 r+1 处删除。加入扫描线数组时,按照 ( l , i ) , ( r + 1 , − i ) (l, i), (r + 1, -i) (l,i),(r+1,i) 加入并排序,这样子的好处是,在同一个位置,删除线段的活动总是优先于加入线段的活动,因为 s e c o n d second second 为负。

每次加入一条新的线段时,此时还未删除的线段一定都与其相交(它们的 l < l i , r ≥ l i l < l_i, r \geq l_i l<li,rli
我们只需要找到其按 a i a_i ai 排序的前驱后继线段,连边即可。

这样子连边的数量是 O ( n ) O(n) O(n) 的,总时间复杂度为: O ( n log ⁡ n ) O(n \log n) O(nlogn)

#include<bits/stdc++.h>
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;
 
const int INF=0x3f3f3f3f;
const long long INFLL=1e18;
 
typedef long long ll;
 
struct node{
    int l;
    int r;
    int w;
};
 
struct Edge{
    int u;
    int v;
    int w;
};
 
const int N = 500005;
 
int fa[N];
std::vector<Edge> edges;
 
int find(int x){
    if(x == fa[x]) return x;
    return fa[x] = find(fa[x]);
}
 
void solve(){
    int n;
    std::cin >> n;
    std::vector<node> a(n + 1);
    fore(i, 1, n + 1) std::cin >> a[i].l >> a[i].r >> a[i].w;
 
    std::vector<std::pair<int, int>> b;
    fore(i, 1, n + 1){
        b.push_back({a[i].l, i});
        b.push_back({a[i].r + 1, -i});
    }
    std::sort(ALL(b));
 
    edges.clear();
    std::set<std::pair<int, int>> st;
    for(auto [p, i] : b){
        if(i < 0){
            st.erase({a[-i].w, -i});
        }
        else{
            auto it = st.lower_bound({a[i].w, 0});
            if(it != st.end()) edges.push_back({i, it -> se, it -> fi - a[i].w});
            if(it != st.begin()){
                --it;
                edges.push_back({i, it -> se, a[i].w - it -> fi});
            }
            st.insert({a[i].w, i});
        }
    }
 
    std::sort(ALL(edges), [](const Edge& lhs, const Edge& rhs){
        return lhs.w < rhs.w;
    });
 
    fore(i, 1, n + 1) fa[i] = i;
    
    ll ans = 0;
    int cnt = 0;
    for(auto [u, v, w] : edges){
        int fau = find(u), fav = find(v);
        if(fau == fav) continue;
        ans += w;
        fa[fau] = fav;
        ++cnt;
        if(cnt == n - 1) break;
    }
    
    std::cout << (cnt == n - 1 ? ans : -1) << endl;
}
 
int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int t;
    std::cin >> t;
    while(t--){
        solve();
    }
    return 0;
}
  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值