bzoj 4071 [Apio2015]巴邻旁之桥 splay

500题留念:
这里写图片描述

对于不过桥的人在最后加进答里就行了。
一个人过桥一定是先从a到桥再从桥到b。

对于K=1的情况,设桥的位置为x,答案就是 |a[i]x|+|b[i]x| 。因此选在所有a和b的中间最优。
对于K!=1的情况,考虑如果当前点选的桥坐标为x,那么对于这个点,答案是 ba+2x (a,b都在桥左), |ba| (a,b跨桥), a+b2x (a,b都在桥右)。
因此可以发现当桥的位置为 a+b2 时最优,离 a+b2 越远越不优。
因此每个点选离 a+b2 最近的桥。将所有点按 a+b2 排序。那么一个前缀的点去第一个桥,剩下的后缀的点去第二个桥。
然后在前缀和后缀内用K=1时的方法。
用两个平衡树维护前缀和后缀。支持插入,删除,查询第K大,求子树点到定点的距离(维护区间的size和区间所有点到inf和-inf的距离和)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define which(x) (ch[fa[x]][1]==x)
const int inf=1e9+10,N=110000;
int K,n,cnt;
ll ans,fin;
char s1[11],s2[11];
struct node
{
    int x,y;
    node(){}
    node(int x,int y):x(x),y(y){}
    friend bool operator < (const node &r1,const node &r2)
    {return r1.x+r1.y<r2.x+r2.y;}
}a[N];
struct Splay
{
    int cnt,root;
    int size[N<<1],ch[N<<1][2],fa[N<<1],val[N<<1],num[N<<1];
    ll sl[N<<1],sr[N<<1];
    void pushup(int x)
    {
        sl[x]=sl[ch[x][0]]+sl[ch[x][1]]+(ll)num[x]*(inf+val[x]);
        sr[x]=sr[ch[x][0]]+sr[ch[x][1]]+(ll)num[x]*(inf-val[x]);
        size[x]=size[ch[x][0]]+size[ch[x][1]]+num[x];
    }
    Splay()
    {
        cnt=2;root=1;
        ch[1][0]=2;fa[2]=1;
        val[2]=-inf;val[1]=inf;
        num[1]=num[2]=1;
        pushup(2);pushup(1);
    }
    void rotate(int x)
    {
        int y=fa[x],k=which(x);
        ch[y][k]=ch[x][k^1];
        ch[x][k^1]=y;
        ch[fa[y]][which(y)]=x;

        fa[x]=fa[y];fa[y]=x;
        fa[ch[y][k]]=y;
        pushup(y);pushup(x);
    }
    void splay(int x,int tar)
    {
        while(fa[x]!=tar)
        {
            int y=fa[x];
            if(fa[y]==tar)rotate(x);
            else 
            {
                if(which(x)^which(y))
                    rotate(x);
                else rotate(y);
                rotate(x);
            }
        }
        if(!tar)root=x;
    }
    int find_val(int x,int v)
    {
        if(val[x]==v)return x;
        if(v<val[x])return find_val(ch[x][0],v);
        return find_val(ch[x][1],v);
    }
    int find_kth(int x,int K)
    {
        if(K>size[ch[x][0]]&&size[ch[x][0]]+num[x]>=K)
            return x;
        if(size[ch[x][0]]>=K)
            return find_kth(ch[x][0],K);
        return find_kth(ch[x][1],K-size[ch[x][0]]-num[x]);
    }
    void up(int x)
    {
        if(!x)return;
        pushup(x);up(fa[x]);
    }
    void insert(int v)
    {
        int x=root;
        while(1)
        {
            if(val[x]==v)
                {num[x]++;up(x);break;}
            if(v<val[x])
            {
                if(!ch[x][0])
                {
                    ch[x][0]=++cnt;fa[cnt]=x;
                    num[cnt]=size[cnt]=1;val[cnt]=v;
                    up(cnt);break;
                }
                x=ch[x][0];
            }
            else
            {
                if(!ch[x][1])
                {
                    ch[x][1]=++cnt;fa[cnt]=x;
                    num[cnt]=size[cnt]=1;val[cnt]=v;
                    up(cnt);break;  
                }
                x=ch[x][1];
            }
        }
        splay(x,0);
    }
    void del(int v)
    {
        int x=find_val(root,v);
        num[x]--;splay(x,0);
    }
    ll query()
    {
        int x=find_kth(root,size[root]/2);
        splay(x,0);
        return sr[ch[x][0]]-(ll)(inf-val[x])*size[ch[x][0]]
        +sl[ch[x][1]]-(ll)(val[x]+inf)*size[ch[x][1]]-
        (val[x]+inf)-(inf-val[x]);
    }
}tr1,tr2;
int main()
{
    //freopen("tt.in","r",stdin);
    scanf("%d%d",&K,&n);
    for(int i=1,x1,x2;i<=n;i++)
    {
        scanf("%s%d%s%d",&s1,&x1,&s2,&x2);
        if(s1[0]==s2[0])ans+=abs(x1-x2);
        else a[++cnt]=node(x1,x2),ans++;
    }
    sort(a+1,a+1+cnt);
    fin=1ll<<60;
    for(int i=1;i<=cnt;i++)
    {
        tr2.insert(a[i].x);
        tr2.insert(a[i].y);
    }
    fin=min(fin,tr1.query()+tr2.query());
    if(K==1)return printf("%lld\n",fin+ans),0;
    for(int i=1;i<=cnt;i++)
    {
        tr2.del(a[i].x);
        tr2.del(a[i].y);
        tr1.insert(a[i].x);
        tr1.insert(a[i].y);
        fin=min(fin,tr1.query()+tr2.query());
    }
    printf("%lld\n",ans+fin);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值