洛谷 P3644 [APIO2015]八邻旁之桥 解题报告

P3644 [APIO2015]八邻旁之桥

题目描述

一条东西走向的穆西河将巴邻旁市一分为二,分割成了区域\(A\)和区域\(B\)

每一块区域沿着河岸都建了恰好\(1000000001\)栋的建筑,每条岸边的建筑都从\(0\)编号到\(1000000000\)。相邻的每对建筑相隔\(1\)个单位距离,河的宽度也是\(1\)个单位长度。区域\(A\)中的\(i\)号建筑物恰好与区域\(B\)中的\(i\)号建筑物隔河相对。

城市中有\(N\)个居民。第\(i\)个居民的房子在区域\(P_i\)\(S_i\)号建筑上,同时他的办公室坐落在\(Q_i\)区域的\(T_i\)号建筑上。一个居民的房子和办公室可能分布在河的两岸,这样他就必须要搭乘船只才能从家中去往办公室,这种情况让很多人都觉得不方便。为了使居民们可以开车去工作,政府决定建造不超过\(K\)座横跨河流的大桥。

由于技术上的原因,每一座桥必须刚好连接河的两岸,桥梁必须严格垂直于河流,并且桥与桥之间不能相交。

当政府建造最多\(K\)座桥之后,设\(D_i\)表示第\(i\)个居民此时开车从家里到办公室的最短距离。请帮助政府建造桥梁,使得\(D_1 + D_2 + \cdots + D_N\)最小。

输入输出格式

输入格式:

输入的第一行包含两个正整数\(K\)\(N\),分别表示桥的上限数量和居民的数量。

接下来\(N\)行,每一行包含四个参数:\(P_i, S_i, Q_i\)\(T_i\),表示第\(i\)个居民的房子在区域\(P_i\)\(S_i\)号建筑上,且他的办公室位于\(Q_i\)区域的\(T_i\)号建筑上。

输出格式:

输出仅为一行,包含一个整数,表示\(D_1 + D_2 + \cdots + D_N\)的最小值。

说明

所有数据都保证:\(P_i\)\(Q_i\)为字符 “A” 和 “B” 中的一个, \(0 \leq S_i, T_i \leq 1000000000\),同一栋建筑内可能有超过\(1\)间房子或办公室(或二者的组合,即房子或办公室的数量同时大于等于\(1\))。

子任务 1 (8 分) \(K = 1\)

\(1 \leq N \leq 1000\)

子任务 2 (14 分) \(K = 1\)

\(1 \leq N \leq 100000\)

子任务 3 (9 分) \(K = 2\)

\(1 \leq N \leq 100\)

子任务 4 (32 分) \(K = 2\)

\(1 \leq N \leq 1000\)

子任务 5 (37 分) \(K = 2\)

\(1 \leq N \leq 100000\)


家和办公室在同一列的可以先处理,过河只会过一次可以先处理

对于剩下的点对\((l,r)\),若桥放在位置\(pos\),则距离总和为

\(\sum_{i=1}^m |l_i-pos|+|r_i-pos|\)\(m\)为有用的点对的个数

我们发现家和办公室对答案的贡献计算方法是没有区别的

则对\(k==1\)
问题就转换成了,在区间上选择一个点,使区间上的所有点到这个点的距离和最短。

是一个贪心问题,我们取这个区间最中间那个点即可。

\(k==2\)

我们思考哪个桥管哪些点

如果一个桥在点对\((l,r)\)中间,那么走哪个都是无所谓的

若果不在,则答案为\(|pos \times 2-l-r|=|pos \times 2-(l+r)|\)\((l+r)\)离哪个\(pos\)近,就会走哪个桥。

于是我们可以以\((l+r)\)为关键字进行排序,枚举端点进行统计。

如何快速统计呢?我们需要支持一个删减元素,查询区间和查询中位数的数据结构

平衡树和主席树都不错

事实上主席树常数会优秀很多

这里写的的fhq_treap


Code:

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <algorithm>
#include <cstring>
#define ll long long
#define ls ch[now][0]
#define rs ch[now][1]
using namespace std;
const int N=200010;
int ch[N][2],val[N],tot;
ll dat[N],siz[N],sum[N];
struct node
{
    ll s,t,k;
    bool friend operator <(node n1,node n2)
    {
        return n1.k<n2.k;
    }
    node(ll s,ll t,ll k)
    {
        this->s=s;
        this->t=t;
        this->k=k;
    }
    node(){}
}e[N];
void updata(int now)
{
    siz[now]=siz[ls]+siz[rs]+1;
    sum[now]=sum[ls]+sum[rs]+dat[now];
}
int k0,n,m,root;
ll ans;
void split(int now,ll k,int &x,int &y)
{
    if(!now) {x=y=0;return;}
    if(dat[now]<=k)
        x=now,split(rs,k,rs,y);
    else
        y=now,split(ls,k,x,ls);
    updata(now);
}
int Merge(int x,int y)
{
    if(!x||!y) return x+y;
    if(val[x]<val[y])
    {
        ch[x][1]=Merge(ch[x][1],y);
        updata(x);
        return x;
    }
    else
    {
        ch[y][0]=Merge(x,ch[y][0]);
        updata(y);
        return y;
    }
}
int new_node(ll k)
{
    dat[++tot]=k;val[tot]=rand();siz[tot]=1;sum[tot]=k;
    return tot;
}
void Insert(ll k)
{
    int x,y;
    split(root,k,x,y);
    root=Merge(x,Merge(new_node(k),y));
}
void extrack(ll k)
{
    int x,y,z;
    split(root,k,x,y);
    split(x,k-1,x,z);
    z=Merge(ch[z][0],ch[z][1]);
    root=Merge(x,Merge(z,y));
}
ll Rank(int now,int x)//查询排名为x的点
{
    if(!now) return 0;
    if(x<=siz[ls]) return Rank(ls,x);
    else if(x>siz[ls]+1) return Rank(rs,x-siz[ls]-1);
    else return dat[now];
}
ll query(ll k)//返回小于等于k的点的权值之和
{
    int x,y,Siz=siz[root]>>1;
    ll s=0;
    split(root,k-1,x,y);
    s=sum[x]<<1;
    s+=k*(Siz-siz[x])<<1;
    root=Merge(x,y);
    return s;
}
ll cal()
{
    ll k=Rank(root,siz[root]>>1);
    return sum[root]-query(k);
}
ll Ans[N];
void work2()
{
    root=tot=0;
    memset(ch,0,sizeof(ch));
    ll sum0=Ans[m];
    for(register int i=m;i;i--)
    {
        Insert(e[i].s);
        Insert(e[i].t);
        sum0=min(sum0,cal()+Ans[i-1]);
    }
    printf("%lld\n",sum0+ans);
}
int main()
{
    srand(time(0));
    scanf("%d%d",&k0,&n);
    ll s,t;char p,q;
    for(register int i=1;i<=n;i++)
    {
        scanf("\n");
        scanf("%c%lld %c%lld",&p,&s,&q,&t);
        if(p==q)
            ans+=abs(t-s);
        else
        {
            ans++;
            node tt(s,t,s+t);
            e[++m]=tt;
        }
    }
    sort(e+1,e+1+m);
    for(int i=1;i<=m;i++)
    {
        Insert(e[i].s);
        Insert(e[i].t);
        Ans[i]=cal();
    }
    if(k0==1) printf("%lld\n",Ans[m]+ans);
    else work2();
    return 0;
}

2018.7.29

转载于:https://www.cnblogs.com/butterflydew/p/9385979.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值