【bzoj4071】[Apio2015]巴邻旁之桥 Treap

题目描述

一条东西走向的穆西河将巴邻旁市一分为二,分割成了区域 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 和 N,分别表示桥的上限数量和居民的数量。

接下来 N 行,每一行包含四个参数:Pi,Si,Qi 和 Ti,表示第 i 个居民的房子在区域 Pi 的 Si 号建筑上,且他的办公室位于 Qi 区域的 Ti 号建筑上。

输出

输出仅为一行,包含一个整数,表示 D1+D2+⋯+DN 的最小值。

样例输入

1 5
B 0 A 4
B 1 B 3
A 5 B 7
B 2 A 6
B 1 A 7

样例输出

24


题解

Treap

先把不需要过桥的距离、以及走在桥上的距离(河的宽度也是 1 个单位长度),把需要过桥的存到一个结构体中。

当k=1时,显然就是要最小化$\sum\limits_{i=1}^n(|s_i-p|+|t_i-p|)$,显然将s和t放到同一个数组里排序,取出中位数即为p。

当k=2时,考虑过桥的两种情况:s和t在桥的同侧、s和t在桥的两侧。

当两座桥都使得s和t在桥的同侧时,距离都为$|s_i+t_i-2p|$,这个值越小越好。

当某座桥使得s和t在桥的两侧时,显然要走这座桥,距离为$|s_i-t_i|$,且这座桥的$|s_i+t_i-2p|$要小于使得s和t在桥的同侧时的$|s_i+t_i-2p|$。

综上所述,路线是和$s_i+t_i$相关的。我们可以枚举一个临界点,$s_i+t_i$小于这个临界点的都走第一座桥,$s_i+t_i$大于这个临界点的都走第二座桥。

那么相当于左右两个和k=1相同的问题。这里需要使用Treap维护区间中位数和区间绝对值和。

注意k=2时也要讨论只建一座桥的情况。

时间复杂度为$O(n\log n)$。

另外我代码写丑了,Treap的话不需要写结构体就可以开多个树。这里懒得改了。

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define N 200010
using namespace std;
typedef long long ll;
struct data
{
	ll px , py , ps;
}a[N];
struct treap
{
	int l[N] , r[N] , cnt[N] , si[N] , rnd[N] , root , tot;
	ll w[N] , sum[N];
	void pushup(int k)
	{
		si[k] = si[l[k]] + si[r[k]] + cnt[k] , sum[k] = sum[l[k]] + sum[r[k]] + cnt[k] * w[k];
	}
	void zig(int &k)
	{
		int t = l[k];
		l[k] = r[t] , r[t] = k , pushup(k) , pushup(t) , k = t;
	}
	void zag(int &k)
	{
		int t = r[k];
		r[k] = l[t] , l[t] = k , pushup(k) , pushup(t) , k = t;
	}
	void insert(int &k , ll x)
	{
		if(!k)
		{
			k = ++tot , si[k] = cnt[k] = 1 , sum[k] = w[k] = x , rnd[k] = rand();
			return;
		}
		si[k] ++ , sum[k] += x;
		if(x == w[k]) cnt[k] ++ ;
		else if(x < w[k])
		{
			insert(l[k] , x);
			if(rnd[l[k]] < rnd[k]) zig(k);
		}
		else
		{
			insert(r[k] , x);
			if(rnd[r[k]] < rnd[k]) zag(k);
		}
	}
	void erase(int &k , ll x)
	{
		si[k] -- , sum[k] -= x;
		if(x == w[k])
		{
			if(cnt[k] > 1) cnt[k] -- ;
			else if(!l[k] || !r[k]) k = l[k] + r[k];
			else if(rnd[l[k]] < rnd[r[k]]) zig(k) , erase(k , x);
			else zag(k) , erase(k , x);
		}
		else if(x < w[k]) erase(l[k] , x);
		else erase(r[k] , x);
	}
	ll find(int k , int x)
	{
		if(x <= si[l[k]]) return find(l[k] , x);
		else if(x > si[l[k]] + cnt[k]) return find(r[k] , x - si[l[k]] - cnt[k]);
		else return w[k];
	}
	ll query(int k , ll x)
	{
		if(!k) return 0;
		else if(x < w[k]) return sum[r[k]] - x * si[r[k]] + cnt[k] * (w[k] - x) + query(l[k] , x);
		else if(x > w[k]) return x * si[l[k]] - sum[l[k]] + cnt[k] * (x - w[k]) + query(r[k] , x);
		else return x * si[l[k]] - sum[l[k]] + sum[r[k]] - x * si[r[k]];
	}
}A , B;
char s1[5] , s2[5];
ll v[N];
bool cmp(data a , data b)
{
	return a.ps < b.ps;
}
int main()
{
	int k , n , i , num = 0;
	ll x , y , ans = 0 , sum = 0 , minn = 0x7fffffffffffffffll;
	scanf("%d%d" , &k , &n);
	for(i = 1 ; i <= n ; i ++ )
	{
		scanf("%s%lld%s%lld" , s1 , &x , s2 , &y);
		if(s1[0] == s2[0]) ans += abs(x - y);
		else a[++num].px = x , a[num].py = y , a[num].ps = a[num].px + a[num].py , ans ++ ;
	}	
	for(i = 1 ; i <= num ; i ++ ) v[i] = a[i].px , v[i + num] = a[i].py;
	sort(v + 1 , v + 2 * num + 1);
	for(i = 1 ; i <= 2 * num ; i ++ ) sum += abs(v[i] - v[num]);
	if(k == 2)
	{
		sort(a + 1 , a + num + 1 , cmp);
		for(i = 1 ; i <= num ; i ++ ) B.insert(B.root , a[i].px) , B.insert(B.root , a[i].py);
		for(i = 1 ; i < num ; i ++ )
		{
			A.insert(A.root , a[i].px) , A.insert(A.root , a[i].py) , B.erase(B.root , a[i].px) , B.erase(B.root , a[i].py);
			minn = min(minn , A.query(A.root , A.find(A.root , A.si[A.root] / 2)) + B.query(B.root , B.find(B.root , B.si[B.root] / 2)));
		}
	}
	printf("%lld\n" , ans + min(sum , minn));
	return 0;
}

 

 

转载于:https://www.cnblogs.com/GXZlegend/p/7043434.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值