Address
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
<
0
b<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
<
r
T<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;
}