您的位置:首页 > 其它

BZOJ 4311: 向量(线段树分治+凸包+三分)

2017-11-05 14:34 387 查看

Description

你要维护一个向量集合,支持以下操作:

1.插入一个向量(x,y)

2.删除插入的第i个向量

3.查询当前集合与(x,y)点积的最大值是多少。如果当前是空集输出0

Input

第一行输入一个整数n,表示操作个数

接下来n行,每行先是一个整数t表示类型,如果t=1,输入向量

(x,y);如果t=2,输入id表示删除第id个向量;否则输入(x,y),查询

与向量(x,y)点积最大值是多少。

保证一个向量只会被删除一次,不会删没有插入过的向量

Output

对于每条t=3的询问,输出一个答案

Sample Input

5

1 3 3

1 1 4

3 3 3

2 1

3 3 3

Sample Output

18

15

HINT

n<=200000 1<=x,y<=10^6

Solution

这题原本是我在JZOJ上找到的,现在由于我没有再在JZ充饭卡,我就被踢了……QAQ

话说这题想了我好久,首先从代数性质入手,死磕x1x2+y1y2这条式子,发现根本不行。每项不独立该怎么整啊?于是开始联系计算几何的相关性质。首先,a⃗ 和b⃗ 的点积是|a⃗ ||b⃗ |cosα,这是一个向量的模乘上另一个向量在此向量上的投影。而读入的待求向量已经确定了一个模,剩下一个向量在上面的投影。我们要使其最大。

对于一个待求向量,我们做一条垂线,从无穷远处挪向原点。根据投影的定义,明显垂线第一个遇到的点所代表的向量就是我们要找的。然而,这个向量必然处于所有向量的最外侧,就是在所有点外围的凸包上。

由于本题向量都在第一象限,所以只用保存上凸壳即可。由于上凸壳斜率单调,然后我们在上凸壳上三分极值即可。现在的问题在于我们每插一个,删一个都去合并凸包显然不是行的。于是我们考虑线段树分治

我们保存每个向量出现的时间区间[L,R]。明显它只会对处于这段时间里的询问起贡献。我们将每个向量插入到线段树的logN个节点上,然后在每个节点都开个链表储存出现在这个节点代表时间区间上的向量。然后我们遍历整颗线段树,在每个节点都用扫描法求出对应的上凸壳。我们可以从下往上处理,也可以从上往下做。在处理完当前节点后,我们要对能贡献的询问贡献答案。这里就在上凸壳上三分,寻找点积最大值更新答案就行了。

考虑这样分治的时间,对于每个询问,都要自顶向下查询logN个节点,每个节点都需要排序求凸包并三分一次,虽然一起处理询问可以同时求凸包,但三分的时间省不掉。所以时间复杂度是O(Nlog2N)的。

另外网上有人的做法是将向量与询问提前排序,根据决策点单调什么的,又归并排序,时间复杂度就会降为O(NlogN),我也不太懂,米娜有兴趣的话可以自己上网查阅=。=

Code

#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#define maxn 200010
#define maxc 18

using namespace std;

typedef long long LL;
int n, tot, top, cur, head[maxn<<2], st[maxn];
struct Data{
int obj, nxt;
}List[maxn*maxc];

struct Ask{
LL x, y;
LL ans;
}q[maxn];

struct Vector{
LL x, y;
Vector() {}
Vector(int _x, int _y):x(_x), y(_y) {}

bool operator < (const Vector& Q) const{
return (x < Q.x) || (x == Q.x && y < Q.y);
}
friend Vector operator - (Vector A, Vector B){
return Vector(A.x - B.x, A.y - B.y);
}
friend LL Dot(Vector A, Vector B){
return A.x * B.x + A.y * B.y;
}
friend LL Det(Vector A, Vector B){
return A.x * B.y - A.y * B.x;
}

}p[maxn], Ch[maxn], tmp[maxn];

void Insert(int root, int L, int R, int x, int y, int k){
if(x > R || y < L)  return;
if(x <= L && y >= R){
List[++cur].nxt = head[root];
List[cur].obj = k;
head[root] = cur;
return;
}

int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
Insert(Lson, L, mid, x, y, k);
Insert(Rson, mid+1, R, x, y, k);
}

void query(int k){

Vector me = Vector(q[k].x, q[k].y);

int L = 1, R = top;
while(L + 2 < R){
int mid1 = L + (R - L) / 3, mid2 = R - (R - L) / 3;
LL Val1 = Dot(Ch[mid1], me), Val2 = Dot(Ch[mid2], me);
if(Val1 == Val2)  L = mid1, R = mid2;
else if(Val1 < Val2)  L = mid1;
else  R = mid2;
}

for(int i = L; i <= R; i++)  q[k].ans = max(q[k].ans, Dot(Ch[i], me));
}

void Solve(int root, int L, int R){

if(L < R){
int mid = (L + R) >> 1, Lson = root << 1, Rson = root << 1 | 1;
Solve(Lson, L, mid);  Solve(Rson, mid+1, R);
}

int cnt = 0;
for(int i = head[root]; i; i = List[i].nxt)  tmp[++cnt] = p[List[i].obj];

if(!cnt)  return;
sort(tmp+1, tmp+cnt+1);

top = 0;
for(int i = 1; i <= cnt; i++){
while(top > 1 && Det(tmp[i] - Ch[top], Ch[top] - Ch[top-1]) <= 0)  top --;
Ch[++top] = tmp[i];
}

for(int i = L; i <= R; i++)  if(q[i].x)  query(i);
}

int main(){

scanf("%d", &n);

int op, k;
LL x, y;
for(int i = 1; i <= n; i++){
scanf("%d", &op);
if(op == 1){
scanf("%lld%lld", &x, &y);
p[++tot] = Vector(x, y);
st[tot] = i;
}
else if(op == 2){
scanf("%d", &k);
Insert(1, 1, n, st[k], i, k);
st[k] = 0;
}
else  scanf("%lld%lld", &q[i].x, &q[i].y);
}

for(int i = 1; i <= tot; i++)  if(st[i])  Insert(1, 1, n, st[i], n, i);

Solve(1, 1, n);

for(int i = 1; i <= n; i++)  if(q[i].x)  printf("%lld\n", q[i].ans);

return 0;
}




为你闯出的前方

贯穿世界的消亡

将弱小的自己藏匿抹杀
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: