P3644 [APIO2015] 巴邻旁之桥

P3644 [APIO2015] 巴邻旁之桥

题目描述

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

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

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

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

当政府建造最多 K 座桥之后,设 Di 表示第 i 个居民此时开车从家里到办公室的最短距离。请帮助政府建造桥梁,使得 D1+D2++DN 最小。

数据范围

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

Solution:

问题转化:

首先我们先来处理一下路径,我们发现河的两岸这一限制并不重要,如果一个人上班不过桥,贡献直接就是 |TS|。如果他过桥,假设桥建在 x ,那么贡献就是 |xS|+|Tx|+1

所以现在题目变为了:在一条线段上有一些点,你需要确定一个或者两个关键点使得所有点到最近的关键点的距离 Di 的总和最小。

首先我们发现 K=1 时的做法很显然,我们只需要选这些点的中位数就好了。

对于K=2:

回顾一下贡献方程:
|xS|+|Tx|

我们现在假设 ST:

w={TS | x[S,T]TS+2(Sx)  x<STS+2(xT)  T<x

整理得:

w={TS | x[S,T]2(midx)  x<S2(xmid)  T<x

我们发现,一个任务 (S,T) 会去到哪个关键点是由其中点 mid 到关键点 x 的距离决定的。所以我们只需要按照中点的坐标排序,就可以枚举一个分割点 pos 保证排序后标号在 [1,pos] 的点走左边的桥 (pos,tot] 的点走右边的桥。

所以我们需要动态维护一个数据结构,支持在线插入或删除一个点,区间求和,求中位数。

至于答案统计:

我们对每线段树找出中位数 mid 之后,查询 [1,mid] 上的和 sum ,点的个数 cnt .该部分贡献为 sum+cnt×mid.(mid,inf] 部分类似。

值得注意的是我们应该求出区间上点的个数 cnt 。以统计答案,而非简单的用整颗线段树上点的总数的一半。

Code:

#include<bits/stdc++.h>
#define ll long long
const int N=2e5+5;
const int inf=1e9+1;
using namespace std;
struct Segment_Tree{
int cnt,rt;
struct Tree{
int ls,rs,cnt;
ll sum;
}t[N*32];
void insert(int &x,int l,int r,int pos,int k)
{
t[x=(x ? x : ++cnt)].cnt+=k;t[x].sum+=k*pos;
if(l==r)return;int mid=l+r>>1;
if(pos<=mid)insert(t[x].ls,l,mid,pos,k);
if(mid<pos)insert(t[x].rs,mid+1,r,pos,k);
}
int query_mid(int x,int l,int r,int k)
{
if(!x||!t[x].cnt)return 0;
if(l==r)return l;int mid=l+r>>1;
if(t[t[x].ls].cnt<k)return query_mid(t[x].rs,mid+1,r,k-t[t[x].ls].cnt);
else return query_mid(t[x].ls,l,mid,k);
}
ll query_sum(int x,int l,int r,int L,int R)
{
if(!x||!t[x].sum)return 0;
if(L<=l&&r<=R)return t[x].sum;
int mid=l+r>>1;ll res=0;
if(L<=mid)res+=query_sum(t[x].ls,l,mid,L,R);
if(mid<R)res+=query_sum(t[x].rs,mid+1,r,L,R);
return res;
}
ll query_cnt(int x,int l,int r,int L,int R)
{
if(!x||!t[x].sum)return 0;
if(L<=l&&r<=R)return t[x].cnt;
int mid=l+r>>1;ll res=0;
if(L<=mid)res+=query_cnt(t[x].ls,l,mid,L,R);
if(mid<R)res+=query_cnt(t[x].rs,mid+1,r,L,R);
return res;
}
int Mid()
{
int k=t[rt].cnt>>1;
return query_mid(rt,1,inf,k);
}
ll calc()
{
int mid=Mid(),k=t[rt].cnt>>1;ll res=0,tmp,cnt;
cnt=query_cnt(rt,1,inf,1,mid);tmp=-query_sum(rt,1,inf,1,mid)+1ll*cnt*mid;
res+=tmp;
cnt=query_cnt(rt,1,inf,mid+1,inf);tmp=query_sum(rt,1,inf,mid+1,inf)-1ll*cnt*mid;
res+=tmp;
return res;
}
}T1,T2;
struct task{
int l,r;
bool operator<(const task &t)const{
return l+r<t.l+t.r;
}
}q[N];
int n,k,tot;
char c[2];
ll ans,tmp;
void work()
{
for(int i=1,x,y;i<=n;i++)
{
cin>>c[0]>>x>>c[1]>>y;
if(x>y)swap(x,y);
if(c[0]==c[1])ans+=y-x;
else{q[++tot]={++x,++y};}
}
sort(q+1,q+1+tot);ans+=tot;
for(int i=1;i<=tot;i++)
{
int l=q[i].l,r=q[i].r;
T1.insert(T1.rt,1,inf,l,1);
T1.insert(T1.rt,1,inf,r,1);
}
tmp=T1.calc();
if(k==1){cout<<ans+tmp;return;}
for(int i=tot;i>=1;i--)
{
int l=q[i].l,r=q[i].r;
T1.insert(T1.rt,1,inf,l,-1);T2.insert(T2.rt,1,inf,l,1);
T1.insert(T1.rt,1,inf,r,-1);T2.insert(T2.rt,1,inf,r,1);
ll res=T1.calc()+T2.calc();
tmp=min(tmp,res);
}
ans+=tmp;
cout<<ans;
}
int main()
{
//freopen("P3644.in","r",stdin);freopen("P3644.out","w",stdout);
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>k>>n;
work();
return 0;
}
posted @   liuboom  阅读(2)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示