bzoj 4071: [Apio2015]巴邻旁之桥 线段树

题意

一条东西走向的穆西河将巴邻旁市一分为二,分割成了区域 A 和区域 B。
每一块区域沿着河岸都建了恰好 1000000001 栋的建筑,每条岸边的建筑都从 0 编号到 1000000000。相邻的每对建筑相隔 1 个单位距离,河的宽度也是 1 个单位长度。区域 A 中的 i 号建筑物恰好与区域 B 中的 i 号建筑物隔河相对。
城市中有 N 个居民。第 i 个居民的房子在区域 Pi 的 Si 号建筑上,同时他的办公室坐落在 Qi 区域的 Ti 号建筑上。一个居民的房子和办公室可能分布在河的两岸,这样他就必须要搭乘船只才能从家中去往办公室,这种情况让很多人都觉得不方便。为了使居民们可以开车去工作,政府决定建造不超过 K 座横跨河流的大桥。
由于技术上的原因,每一座桥必须刚好连接河的两岸,桥梁必须严格垂直于河流,并且桥与桥之间不能相交。当政府建造最多 K 座桥之后,设 Di 表示第 i 个居民此时开车从家里到办公室的最短距离。请帮助政府建造桥梁,使得 D1+D2+⋯+DN 最小。
K=2,1≤N≤100000

分析

首先问题可以转化成给n个区间,然后在数轴上找两个点,使得所有区间到离其最近的点的距离和最小。
如果k=1的话,直接找中位数即可。
若k=2的话,我们可以把区间按照中位数来排序,那么一定有一个最优的分割点满足前缀全部选第一个点,后缀全部选第二个点。
那么只要枚举分割点然后用权值线段树来维护中位数即可。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=200005;

int l[N],r[N],n,m,a[N],a1,f1;
struct data{double val;int id;}f[N];

struct Sig_tree
{
    int root,sz;
    struct tree{int l,r,size;LL s;}t[N*30];

    void ins(int &d,int l,int r,int x,int y)
    {
        if (!d) d=++sz;
        t[d].s+=x*y;t[d].size+=y;
        if (l==r) return;
        int mid=(l+r)/2;
        if (x<=mid) ins(t[d].l,l,mid,x,y);
        else ins(t[d].r,mid+1,r,x,y);
    }

    int kth(int d,int l,int r,int k)
    {
        if (l==r) return l;
        int mid=(l+r)/2;
        if (t[t[d].l].size>=k) return kth(t[d].l,l,mid,k);
        else return kth(t[d].r,mid+1,r,k-t[t[d].l].size);
    }

    LL query(int d,int l,int r,int w)
    {
        if (r<=w) return (LL)w*t[d].size-t[d].s;
        if (l>=w) return t[d].s-(LL)w*t[d].size;
        int mid=(l+r)/2;
        return query(t[d].l,l,mid,w)+query(t[d].r,mid+1,r,w);
    }
}t1,t2;

void solve1()
{
    LL ans=0;
    for (int i=1;i<=n;i++)
    {
        int x,y;
        char ch1[2],ch2[2];
        scanf("%s%d%s%d",&ch1,&x,&ch2,&y);
        if (x>y) swap(x,y);
        if (ch1[0]==ch2[0]) ans+=y-x;
        else a[++a1]=x,a[++a1]=y,ans++;
    }
    sort(a+1,a+a1+1);
    int mid=a[(a1+1)/2];
    for (int i=1;i<=a1;i++) ans+=abs(a[i]-mid);
    printf("%lld",ans);
}

bool cmp(data a,data b)
{
    return a.val<b.val;
}

LL query()
{
    int w1=t1.kth(t1.root,0,1000000000,(t1.t[t1.root].size+1)/2),w2=t2.kth(t2.root,0,1000000000,(t2.t[t2.root].size+1)/2);
    return t1.query(t1.root,0,1000000000,w1)+t2.query(t2.root,0,1000000000,w2);
}

void solve2()
{
    LL ans=0,mn=1e17;
    for (int i=1;i<=n;i++)
    {
        char ch1[2],ch2[2];
        scanf("%s%d%s%d",&ch1,&l[i],&ch2,&r[i]);
        if (l[i]>r[i]) swap(l[i],r[i]);
        if (ch1[0]==ch2[0]) ans+=r[i]-l[i],i--,n--;
        else f[++f1].val=1.0*(l[i]+r[i])/2,f[f1].id=i,ans++;
    }
    sort(f+1,f+f1+1,cmp);
    for (int i=1;i<=f1;i++)
    {
        t2.ins(t2.root,0,1000000000,l[f[i].id],1);
        t2.ins(t2.root,0,1000000000,r[f[i].id],1);
    }
    mn=query()+ans;
    for (int i=1;i<f1;i++)
    {
        t2.ins(t2.root,0,1000000000,l[f[i].id],-1);
        t2.ins(t2.root,0,1000000000,r[f[i].id],-1);
        t1.ins(t1.root,0,1000000000,l[f[i].id],1);
        t1.ins(t1.root,0,1000000000,r[f[i].id],1);
        mn=min(mn,query()+ans);
    }
    printf("%lld",mn);
}

int main()
{
    scanf("%d%d",&m,&n);
    if (m==1) solve1();
    else solve2();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值