bzoj4071(UOJ112)【APIO2015】Palembang Bridges (权值线段树)

Problem

n n 个人,住在 A B B 岸,在 A B B 岸上班,距离为坐标相减(如需过河则加一)。
可以修建 k(k<=2) 座桥,问所有人从家到公司所需最短时间和是多少

Solution

发现 k k 只有两种取值,我们分别讨论。

  • k=1

    当我们选择 pos p o s 为桥时,所得的结果是 ans=i=1nabs(xipos)+abs(yipos) a n s = ∑ i = 1 n a b s ( x i − p o s ) + a b s ( y i − p o s )
    显然,将桥选在中间为最优。
    因此将所有数(两岸)一起排序,中位数就是桥的位置。

    • k=2 k = 2

    上面我们发现中位数为最优,那么两座桥的时候,人会怎么选择呢?
    显然人会走向距离 xi+yi2 x i + y i 2 最近的
    因此我们可以把所有人按 xi+yi2 x i + y i 2 排序,前一部分人走左桥,有一部分人走右桥。
    那么问题就转化为区间求中位数。
    显然可以平衡树实现(常数大+代码长…)
    而权值线段树也可以解决(因为只会求前一段、后一段,中位数查个数就可以了)

    对于每一段的结果,如前一段为例,
    ans=poscnt1sum1+sum2poscnt2=poscnt1sum1+(sumsum1)pos(cntcnt1)=pos(2cntcnt1)+sum2sum1 a n s = p o s ∗ c n t 1 − s u m 1 + s u m 2 − p o s ∗ c n t 2 = p o s ∗ c n t 1 − s u m 1 + ( s u m − s u m 1 ) − p o s ∗ ( c n t − c n t 1 ) = p o s ∗ ( 2 ∗ c n t − c n t 1 ) + s u m − 2 ∗ s u m 1
    (其中 pos p o s 为中位数, sum1 s u m 1 为小于中位数的数的和, sum2 s u m 2 为小于中位数的数的和, sum s u m 为这一部分所有数的和, cnt1 c n t 1 为小于中位数的数的个数, cnt2 c n t 2 为小于中位数的数的个数, cnt c n t 为这一部分所有数的个数)

    注意:

    1. 过河也需要时间…
    2. 权值线段树,因此要离散化
    3. 树中有 2n 2 n 个数,中位数也就是第 n n 小或第 n
    4. 注意空间…离散化以后会有至多 2m 2 m 个点,因此线段树要开 8 8 <script type="math/tex" id="MathJax-Element-101">8</script> 倍
    5. 除了我可能没有人会这样wa…要先按和排序再离散化….先离散化以后和就边了啊啊啊….

    Code

    #include <cstdio>
    #include <cmath>
    #include <algorithm>
    using namespace std;
    #define N 100010
    #define ll long long
    char str1[5],str2[5];
    int num=0,cnt=0,k,n;
    ll b[N<<1];
    ll ans=0;
    struct node{int x,y;}a[N];
    inline bool cmp(node x,node y){return x.x+x.y<y.x+y.y;}
    struct Segment{
        ll s,sum[N<<3];
        int tot,sz[N<<3];
        inline void init(){
            s=0;tot=0;
        }
        inline void ins(int v,int l,int r,int x,int val){
            sz[v]+=val;sum[v]+=b[x]*val;
            if(l==r) return;
            int mid=l+r>>1;
            if(x<=mid) ins(v<<1,l,mid,x,val);
            else ins(v<<1|1,mid+1,r,x,val);
        }
        inline int query(int v,int l,int r,int x){
            if(l==r){
                s+=sum[v];tot+=sz[v];
                return l;   
            }int mid=l+r>>1;
            if(x<=sz[v<<1]) return query(v<<1,l,mid,x);
            s+=sum[v<<1];tot+=sz[v<<1];return query(v<<1|1,mid+1,r,x-sz[v<<1]);
        }
    }t[2];
    int main(){
        scanf("%d%d",&k,&n);
        for(int i=1;i<=n;i++){
            int x,y;scanf("%s%d%s%d",str1,&x,str2,&y);
            if(str1[0]==str2[0]){
                if(x>y) swap(x,y);
                ans=ans+(ll)y-(ll)x;
            }else a[++num].x=x,a[num].y=y,b[++cnt]=x,b[++cnt]=y;
        }ans+=num;
        if(!num){printf("%lld\n",ans);return 0;}
        if(k==1){
            sort(b+1,b+cnt+1);ll x=((ll)b[cnt/2]+(ll)b[cnt/2+1])/2;
            for(int i=1;i<=cnt;i++){
                if(b[i]>x) ans=ans+b[i]-x;
                else ans=ans-b[i]+x;
            }
            printf("%lld\n",ans);
        }else{
            sort(b+1,b+cnt+1);cnt=unique(b+1,b+cnt+1)-b-1;
            sort(a+1,a+num+1,cmp);
            for(int i=1;i<=num;i++){
                a[i].x=lower_bound(b+1,b+cnt+1,a[i].x)-b;
                a[i].y=lower_bound(b+1,b+cnt+1,a[i].y)-b;
                t[1].ins(1,1,cnt,a[i].x,1);t[1].ins(1,1,cnt,a[i].y,1);
            }
            t[1].init();ll pos=b[t[1].query(1,1,cnt,num)];
            ll mn=pos*(2ll*t[1].tot-(ll)t[1].sz[1])+t[1].sum[1]-2*t[1].s;
            for(int i=1;i<=num;i++){
                t[1].ins(1,1,cnt,a[i].x,-1);t[1].ins(1,1,cnt,a[i].y,-1);
                t[0].ins(1,1,cnt,a[i].x,1);t[0].ins(1,1,cnt,a[i].y,1);
                t[0].init();t[1].init();
                pos=b[t[0].query(1,1,cnt,i)];
                ll res=pos*(2ll*t[0].tot-(ll)t[0].sz[1])+t[0].sum[1]-2*t[0].s;
                pos=b[t[1].query(1,1,cnt,num-i)];
                res+=pos*(2ll*t[1].tot-(ll)t[1].sz[1])+t[1].sum[1]-2*t[1].s;
                mn=min(mn,res);
            }
            printf("%lld\n",ans+mn);
        }
        return 0;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值