[BZOJ3533][Sdoi2014]向量集(凸包+线段树+二分)

Address

洛谷P3309
BZOJ3533
LOJ#2197

Solution

先假设询问对象是所有的向量,并且已经全部加入集合。
发现向量 ( x , y ) (x,y) (x,y) 和向量 ( a , b ) (a,b) (a,b) 的点积,就等于过点 ( x , y ) (x,y) (x,y) 的、斜率为 − a b -\frac ab ba 的直线在 y y y 轴上截距的 b b b 倍。
b > 0 b>0 b>0 时,要最大化截距,所以要在集合内所有点的上凸壳上二分找到答案。
b &lt; 0 b&lt;0 b<0 时,要最小化截距,所以要在集合内所有点的下凸壳上二分。
b = 0 b=0 b=0 时,我们只需要最大化 a x ax ax ,所以在上凸壳和下凸壳都行。
如果加入「第 L L L 个到第 R R R 个加入的向量」这一限制,又怎么做呢?
线段树!!!!!
线段树每个节点维护对应区间内的点构成的上下凸壳。
但还有一个困难:每次加入的点的 x x x 坐标没有单调性,所以不能直接增量维护凸壳,而如果每一次更新都将某个节点 [ l , r ] [l,r] [l,r] ,合并 [ l , m i d ] [l,mid] [l,mid] [ m i d + 1 , r ] [mid+1,r] [mid+1,r] 的上下凸壳到 [ l , r ] [l,r] [l,r] ,那么插入的复杂度将是 O ( n ) O(n) O(n) 的,没有任何改进。
但继续思考,我们只需要在末尾加向量而不是修改一个向量,可以怎样优化?
发现:对于每个线段树上的节点对应的区间 [ l , r ] [l,r] [l,r] ,如果已经加入的向量数 T &lt; r T&lt;r T<r ,那么这个点在加入其他向量之前一定不会被使用到。
所以,我们的策略是:线段树上的节点 p p p ,如果 p p p 对应的区间为 [ l , r ] [l,r] [l,r] ,当且仅当 T = r T=r T=r 时才从 p p p 的两个子节点合并。
这样,每个节点都只进行了一次合并。
查询时只需要找到区间 [ L , R ] [L,R] [L,R] 在线段树上拆成的不超过 O ( log ⁡ n ) O(\log n) O(logn) 个区间后在这些区间的 上 / 下 凸壳上二分查找最大值即可。
时间复杂度 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n)

Code

#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define p2 p << 1
#define p3 p << 1 | 1

inline int read()
{
	int res = 0; bool bo = 0; char c;
	while (((c = getchar()) < '0' || c > '9') && c != '-');
	if (c == '-') bo = 1; else res = c - 48;
	while ((c = getchar()) >= '0' && c <= '9')
		res = (res << 3) + (res << 1) + (c - 48);
	return bo ? ~res + 1 : res;
}

inline char get()
{
	char c;
	while ((c = getchar()) != 'A' && c != 'Q');
	return c;
}

template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}

typedef long long ll;

const int N = 4e5 + 5, M = N << 2;

int n, T;

struct point
{
	int x, y;
	
	friend inline point operator - (point a, point b)
	{
		return (point) {b.x - a.x, b.y - a.y};
	}
	
	friend inline ll operator * (point a, point b)
	{
		return 1ll * a.x * b.y - 1ll * a.y * b.x;
	}
};

std::vector<point> up[M], dn[M];
bool isfull[M];
char s[N];

void add_up(int p, point x)
{
	int top = up[p].size() - 1;
	while (top > 0 && (up[p][top - 1] - up[p][top]) * (up[p][top - 1] - x) >= 0)
		top--, up[p].pop_back();
	up[p].push_back(x);
}

void add_dn(int p, point x)
{
	int top = dn[p].size() - 1;
	while (top > 0 && (dn[p][top - 1] - dn[p][top]) * (dn[p][top - 1] - x) <= 0)
		top--, dn[p].pop_back();
	dn[p].push_back(x);
}

void merge_up(int p)
{
	int i, n1 = up[p2].size(), n2 = up[p3].size(), q1 = 0, q2 = 0;
	For (i, 1, n1 + n2)
		if (q2 == n2 || (q1 < n1 &&
			(up[p2][q1].x < up[p3][q2].x || (up[p2][q1].x == up[p3][q2].x
				&& up[p2][q1].y < up[p3][q2].y))))
					add_up(p, up[p2][q1]), q1++;
		else add_up(p, up[p3][q2]), q2++;
}

void merge_dn(int p)
{
	int i, n1 = dn[p2].size(), n2 = dn[p3].size(), q1 = 0, q2 = 0;
	For (i, 1, n1 + n2)
		if (q2 == n2 || (q1 < n1 &&
			(dn[p2][q1].x < dn[p3][q2].x || (dn[p2][q1].x == dn[p3][q2].x
				&& dn[p2][q1].y < dn[p3][q2].y))))
					add_dn(p, dn[p2][q1]), q1++;
		else add_dn(p, dn[p3][q2]), q2++;
}

ll findmax_up(int p, int a, int b)
{
	int l = 0, r = up[p].size() - 2;
	while (l <= r)
	{
		int mid = l + r >> 1;
		if (1ll * up[p][mid].x * a + 1ll * up[p][mid].y * b
			>= 1ll * up[p][mid + 1].x * a + 1ll * up[p][mid + 1].y * b)
				r = mid - 1;
		else l = mid + 1;
	}
	return 1ll * up[p][l].x * a + 1ll * up[p][l].y * b;
}

ll findmax_dn(int p, int a, int b)
{
	int l = 0, r = dn[p].size() - 2;
	while (l <= r)
	{
		int mid = l + r >> 1;
		if (1ll * dn[p][mid].x * a + 1ll * dn[p][mid].y * b
			>= 1ll * dn[p][mid + 1].x * a + 1ll * dn[p][mid + 1].y * b)
				r = mid - 1;
		else l = mid + 1;
	}
	return 1ll * dn[p][l].x * a + 1ll * dn[p][l].y * b;
}

void addpoint(int l, int r, int pos, point x, int p)
{
	if (l == r)
	{
		up[p].push_back(x); dn[p].push_back(x);
		return (void) (isfull[p] = 1);
	}
	int mid = l + r >> 1;
	if (pos <= mid) addpoint(l, mid, pos, x, p2);
	else addpoint(mid + 1, r, pos, x, p3);
	if (isfull[p2] && isfull[p3])
		merge_up(p), merge_dn(p), isfull[p] = 1;
}

ll querymax(int l, int r, int s, int e, int a, int b, int p)
{
	if (l == s && r == e)
		return b >= 0 ? findmax_up(p, a, b) : findmax_dn(p, a, b);
	int mid = l + r >> 1;
	if (e <= mid) return querymax(l, mid, s, e, a, b, p2);
	else if (s >= mid + 1) return querymax(mid + 1, r, s, e, a, b, p3);
	else return Max(querymax(l, mid, s, mid, a, b, p2),
		querymax(mid + 1, r, mid + 1, e, a, b, p3));
}

int main()
{
	int i, l, r, x, y; ll lst = 0; char op;
	n = read(); scanf("%s", s + 1);
	For (i, 1, n)
	{
		op = get(); x = read(); y = read();
		if (s[1] != 'E') x = x ^ (lst & 0x7fffffff),
			y = y ^ (lst & 0x7fffffff);
		if (op == 'A') T++, addpoint(1, n, T, (point) {x, y}, 1);
		else
		{
			l = read(); r = read();
			if (s[1] != 'E')
			{
				l = l ^ (lst & 0x7fffffff);
				r = r ^ (lst & 0x7fffffff);
			}
			printf("%lld\n", lst = querymax(1, n, l, r, x, y, 1));
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值