[HAOI2011] 防线修建 题解
容易发现答案即为重要城市的上凸壳的长度。考虑将正序删点转化为倒序删点,就变成了经典动态加点求凸壳。
对于动态加点求凸壳的问题,我们考虑使用 set 进行维护。因为这样可以较快的找到加入点 \(y\) 在凸壳上的前驱 \(x\) 和后继 \(z\)。我们先判断 \(y\) 是否能加入上凸壳,相当于询问 \(x,y,z\) 三点构成的上凸壳中有没有 \(y\)。若可以加入,就向左、右暴力递推,若自己的前驱/后继不满足要求,就将其删去;否则跳出循环。
考虑证明这样做的时间复杂度正确性。由于每个点最多被删去一次,所以暴力递推的总次数是 \(O(m)\) 的,因此时间复杂度为 \(O(m\log m)\) 的。
#include<bits/stdc++.h>
#define dx(x,y) (xc[x]-xc[y])
#define dy(x,y) (yc[x]-yc[y])
using namespace std;
const int N=1e5+5;
struct idx{int x;};double ans,as[N];
int n,q,xc[N],yc[N],dl[N],vis[N];
bool operator<(idx x,idx y){
return xc[x.x]==xc[y.x]?yc[x.x]<yc[y.x]:xc[x.x]<xc[y.x];
}int tp,num,st[N],id[N],sum;set<idx>s;
int cmp(int x,int y){
return xc[x]==xc[y]?yc[x]<yc[y]:xc[x]<xc[y];
}int check(int x,int y,int z){
return dy(y,x)*dx(z,y)<=dy(z,y)*dx(y,x);
}double line(int x,int y){
return sqrt(pow(dx(x,y),2)+pow(dy(x,y),2));
}double del(int x,int y,int z){
return line(x,y)+line(y,z)-line(x,z);
}void add(int x){
auto itl=s.lower_bound({x}),itr=itl;itl--;
if(check(itl->x,x,itr->x)) return;
ans+=del(itl->x,x,itr->x);
while(1){
itl=s.lower_bound({x}),itr=--itl;
if(itr==s.begin()) break;itl--;
if(!check(itl->x,itr->x,x)) break;
ans-=del(itl->x,itr->x,x),s.erase(itr);
}while(1){
itr=s.upper_bound({x}),itl=itr++;
if(itr==s.end()) break;
if(!check(x,itl->x,itr->x)) break;
ans-=del(x,itl->x,itr->x),s.erase(itl);
}s.insert({x});
}int main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
scanf("%d%d%d%d",&xc[2],&xc[3],&yc[3],&n),n+=3;
for(int i=4;i<=n;i++) scanf("%d%d",&xc[i],&yc[i]);
scanf("%d",&q);
for(int i=1;i<=q;i++){
int opt,c;scanf("%d",&opt);
if(opt<2) scanf("%d",&c),vis[dl[i]=c+3]=1;
}for(int i=1;i<=n;i++) if(!vis[i]) id[++num]=i;
sort(id+1,id+num+1,cmp);
for(int i=1;i<=num;st[++tp]=id[i++])
while(tp>1&&check(st[tp-1],st[tp],id[i])) tp--;
for(int i=1;i<=tp;i++) s.insert({st[i]});
for(int i=1;i<tp;i++) ans+=line(st[i],st[i+1]);
for(int i=q;i;i--){
if(dl[i]>0) add(dl[i]);
else as[++sum]=ans;
}for(int i=sum;i;i--) printf("%.2lf\n",as[i]);
return 0;
}

浙公网安备 33010602011771号