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 leq 200000) (1 leq x,y leq 10^6)
想法
每个向量只对一定范围内的查询操作可能有贡献,于是可以线段树分治。
具体就是将询问按时间编号为 (1~m) ,建一棵线段树。
每个向量 (insert) 到它有贡献的区间中,注意找到完全包含在它的贡献区间的节点后打上标记就行了,不需要下放!
怎么打标记呢?就是对每个节点开一个 (vector) ,把向量扔进去就行了。
接着我们考虑,“求集合中与((x,y)) 点积的最大值” 怎么搞。
对于集合中的两个向量,((u1,v1) 和 (u2,v2)) ,不妨设 (u1<u2)
若前者不如后者,即 ((u1,v1) cdot (x,y) < (u2,v2) cdot (x,y))
打开并整理一下得出 (-frac{x}{y} < frac{v2-v1}{u2-u1})
也就是说我们需要维护斜率递减的凸包。
为了维护方便,我们将向量们按 (x) 坐标从小到大依次 (insert) 到线段树中,线段树的每个节点维护一个凸包。
最终查询的时候,在线段树上从根到特定叶子的路径上每个节点维护的凸包中二分最优解,更新答案。
每次查询复杂度 (O(log^2n)) , 总复杂度 (O(nlog^2n))
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int read(){
int x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
}
const int N = 200005;
typedef long long ll;
int tot,n,m;
struct data{ int x,y,l,r; }d[N],q[N];
bool cmp(data x,data y) { return x.x<y.x; }
int root,cnt;
int ch[N*2][2];
vector<data> v[N*2];
void build(int x,int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
build(ch[x][0]=++cnt,l,mid);
build(ch[x][1]=++cnt,mid+1,r);
}
bool bigk(data a,data b,data c) { return 1ll*(b.y-a.y)*(c.x-b.x)<=1ll*(c.y-b.y)*(b.x-a.x); }
void ins(int x,int l,int r,int L,int R,int c){
if(l==L && r==R){
while(v[x].size()>1 && bigk(v[x][v[x].size()-2],v[x][v[x].size()-1],d[c])) v[x].pop_back();
v[x].push_back(d[c]);
return;
}
int mid=(l+r)>>1;
if(R<=mid) ins(ch[x][0],l,mid,L,R,c);
else if(L>mid) ins(ch[x][1],mid+1,r,L,R,c);
else{
ins(ch[x][0],l,mid,L,mid,c);
ins(ch[x][1],mid+1,r,mid+1,R,c);
}
}
bool check(data a,data b,data c) { return 1ll*c.y*(a.y-b.y)<1ll*c.x*(b.x-a.x); }
ll ask(int x,int l,int r,int c){
ll ret=0;
if(v[x].size()>=1){
int L=0,R=v[x].size()-1,mid;
while(L<R){
mid=(L+R)>>1;
if(check(v[x][mid],v[x][mid+1],q[c])) L=mid+1;
else R=mid;
}
ret=1ll*q[c].x*v[x][L].x+1ll*q[c].y*v[x][L].y;
}
if(l==r) return ret;
int mid=(l+r)>>1;
if(c<=mid) return max(ret,ask(ch[x][0],l,mid,c));
return max(ret,ask(ch[x][1],mid+1,r,c));
}
int main()
{
int Q,t,id,x,y;
Q=read();
while(Q--){
t=read();
if(t==1) {
x=read(); y=read();
d[++m]=(data){x,y,n+1,-1};
}
else if(t==2){
id=read();
d[id].r=n;
}
else {
x=read(); y=read();
q[++n]=(data){x,y,0,0};
}
}
for(int i=1;i<=m;i++) if(d[i].r==-1) d[i].r=n;
sort(d+1,d+1+m,cmp);
build(root=++cnt,1,n);
for(int i=1;i<=m;i++)
if(d[i].l<=d[i].r) ins(root,1,n,d[i].l,d[i].r,i);
for(int i=1;i<=n;i++) printf("%lld
",ask(root,1,n,i));
return 0;
}