Moving Segments

题目大意

有n条水平线段, 左端点为l[i], 右端点为r[i], 可以左右平移, 线段平移的消耗是平移的距离, 现在要你把这些线段平移到存在一条竖直线段与所有水平线段相交, 求最小的总消耗

题目思路

  • nlog(n)

    1. 首先, 能够得到最小消耗的那条竖直线段肯定能够在线段的端点处取到, 如果不在端点处, 将这条竖线向左平移到左边最近的端点, 向右平移到右边最近的端点这两个操作肯定是一个消耗增加, 一个消耗减小, 或者两个都不变, 所以在端点处一定能取到最小值
    2. 最先想到的做法肯定是将所有2n个端点排序, 然后一个端点一个端点扫过去的到最小消耗
    3. 设完全在竖直线段左侧的线段数量为x, 完全在右侧线段数量为y
    4. 那么从一个端点到另一个端点的变化是两端点距离*(x-y), 因为端点排过序, 所以在两个端点移动不会改变x, y(线段只会从被竖线中间穿过或交于一个端点变成交于另一个端点), 所以只要每次移动后再更新x, y就好了
    5. 这样加上排序效率是nlogn
  • 题解还提到了一种O(n)的解法

    1. 很容易看出, 竖线从左到右扫描的时候, 消耗肯定是先减小后增大的, 所以会在中间某个位置取到最小值
    2. 因为从端点i移动到端点i+1(端点排好了序), 消耗的变化是dis(i, i+1)*(x-y); 所以, 当x==y的时候, 就是最小值
    3. 如果x==y, 可能是有n个端点完全在竖线左边, n个端点完全在竖线右边, 还可能是(n-x)完全在竖线左边, (n-x)个端点完全右边, 然后然后有x条线段被竖线穿过, 两种情况, x==y都可以在第n个端点和第n+1个端点中取到
    4. 所以竖线肯定在第n个端点或者第n+1个端点处
    5. 所以只需要找出排序在第n个端点的位置, 然后对每条线段计算需要的平移的距离
      求数组第k大元素是一个经典问题, 有O(n)的求法, STL里面又有nth_element()可以在O(n)求出第k大元素

nth_element()

nth_element(a, a+k, a+n);
之后a[k]就是第k大元素, 并且a[k]之前元素都小于a[k], a[k]之后元素都大于a[k]

代码

#include <bits/stdc++.h>
using namespace std;
int n, l[111111], r[111111], s[222222];
int main(){
    scanf("%d", &n);
    for(int i=0; i<n; ++i){
        scanf("%d%d", l+i, r+i);
        s[i*2] = l[i];
        s[i*2+1] = r[i];
    }
    nth_element(s, s+n-1, s+2*n);
    int t = s[n-1];
    long long ans = 0;
    for(int i=0; i<n; ++i)
        if(r[i]<t) ans += (t-r[i]);
        else if(l[i]>t) ans += (l[i]-t);
    cout << ans << endl;
    return 0;
}

三分

想不到是第n个端点就是能得到最小消耗的竖线位置
但很容易想到消耗是沿端点从左到右先减少后增加的
这样就可以利用三分, 代码也很精简, 而且更容易想到

#include <bits/stdc++.h>
using namespace std;
int n, l[111111], r[111111], s[222222];
long long cal(int p)
{
    long long ans = 0;
    for(int i=0; i<n; ++i)
        if(r[i]<p) ans += p-r[i];
        else if(l[i]>p) ans += l[i]-p;
    return ans;
}
int main(){
    scanf("%d", &n);
    for(int i=0; i<n; ++i){
        scanf("%d%d", l+i, r+i);
        s[i*2] = l[i];
        s[i*2+1] = r[i];
    }
    sort(s, s+2*n);
    int l=0, r=2*n-1, lmid, rmid;
    long long tl, tr;
    while(l<r)
    {
        lmid = l + (r-l)/2;
        rmid = lmid + (r-lmid)/2;
        tl = cal(s[lmid]);
        tr = cal(s[rmid]);
        if(tl<tr) r = rmid-1;
        else if(tl == tr) l = lmid, r = rmid;
        else l = lmid+1;
    }
    cout << cal(s[l]) << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值