【HAOI 2011】防线修建

传送门


Problem

近来 A A A 国和 B B B 国的矛盾激化,为了预防不测, A A A 国准备修建一条长长的防线,当然修建防线的话,肯定要把需要保护的城市修在防线内部了。可是 A A A 国上层现在还犹豫不决,到底该把哪些城市作为保护对象呢?又由于A国的经费有限,所以希望你能帮忙完成如下的一个任务:

  1. 给出你所有的 A A A 国城市坐标。
  2. A A A 国上层经过讨论,考虑到经济问题,决定取消对 i i i 城市的保护,也就是说 i i i 城市不需要在防线内了。
  3. A A A 国上层询问对于剩下要保护的城市,修建防线的总经费最少是多少。

你需要对每次询问作出回答。注意单位 1 1 1 长度的防线花费为 1 1 1

A A A 国的地形是这样的,形如下图, x x x 轴是一条河流,相当于一条天然防线,不需要你再修建。

A A A 国总是有两个城市在河边,一个点是 ( 0 , 0 ) (0,0) (0,0),一个点是 ( n , 0 ) (n,0) (n,0),其余所有点的横坐标均大于 0 0 0 小于 n n n,纵坐标均大于 0 0 0。A国有一个不在 ( 0 , 0 ) (0,0) (0,0) ( n , 0 ) (n,0) (n,0) 的首都。 ( 0 , 0 ) , ( n , 0 ) (0,0),(n,0) (0,0),(n,0) 和首都这三个城市是一定需要保护的。
20170622163416_34324
上图中, A , B , C , D , E A,B,C,D,E A,B,C,D,E 点为 A A A 国城市,且目前都要保护,那么修建的防线就会是 A − B − C − D A-B-C-D ABCD,花费也就是线段 A B AB AB 的长度+线段 B C BC BC 的长度+线段 C D CD CD 的长度。如果,这个时候撤销 B B B 点的保护,那么防线变成下图
20170622163442_85519


Solution

由于删点不好做,我们先把删除操作离线下来,然后反着加点。

现在的问题就是如何维护这个动态凸包。

我们先把求出每个点的极角,这样在后面就可以快速求出一个点的前驱和后继。

分两种情况讨论:

  1. x x x 已经在凸包内,则直接 r e t u r n return return
  2. 若点 x x x 不在凸包内,找到前驱 p r e pre pre 和后继 s u f suf suf,把 ( p r e , s u f ) (pre,suf) (pre,suf) 的边断掉,加入 ( x , p r e ) (x,pre) (x,pre) ( x , s u f ) (x,suf) (x,suf)

要注意的是加入 x x x 可能会使 x , p r e , p r e − 1 x,pre,pre-1 x,pre,pre1 之间的角大于 180 180 180 s u f suf suf 同理),要用 w h i l e while while 判断一下。

由于要快速查找前驱和后继,我们要用平衡树。(代码实现中用的 s e t set set。)


Code

#include<set>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 100005
#define eps 1e-8
#define iterator set<point>::iterator
using namespace std;
bool Del[N];
double now,ans[N];
int n,x,y,m,q;
int Sign(double x)  {return (x>eps)?1:((x<-eps)?-1:0);}
struct point{
	double x,y,angle,len;
	point(){}
	point(double x,double y):x(x),y(y){}
	friend point operator+(const point &p,const point &b)  {return point(p.x+b.x,p.y+b.y);}
	friend point operator-(const point &p,const point &b)  {return point(p.x-b.x,p.y-b.y);}
	friend double dot(const point &p,const point &b)  {return p.x*b.x+p.y*b.y;}
	friend double cross(const point &p,const point &b)  {return p.x*b.y-p.y*b.x;}
	friend double dis(const point &p)  {return sqrt(p.x*p.x+p.y*p.y);}
}p[N];
struct query{int x,op;}Q[N];
bool operator<(const point &p,const point &q)  {return Sign(p.angle-q.angle)==0?p.len<q.len:p.angle<q.angle;}
set<point>Set;
iterator pre,suf,temp;
iterator Prefix(iterator it){
	if(it==Set.begin())  it=Set.end();
	it--;
	return it;
}
iterator Suffix(iterator it){
	it++;
	if(it==Set.end())  it=Set.begin();
	return it;
}
void Insert(point x){
	suf=Set.lower_bound(x);
	if(suf==Set.end())  suf=Set.begin();
	pre=Prefix(suf);
	if(cross((*suf)-(*pre),x-(*pre))>=0)  return;
	now-=dis((*pre)-(*suf));
	now+=dis(x-(*pre))+dis(x-(*suf));
	Set.insert(x);
	temp=Prefix(pre);
	while(cross(x-(*pre),(*pre)-(*temp))>=0){
		now+=dis(x-(*temp));
		now-=dis((*pre)-(*temp))+dis(x-(*pre));
		Set.erase(pre);
		pre=temp,temp=Prefix(pre);
	}
	temp=Suffix(suf);
	while(cross(x-(*suf),(*suf)-(*temp))<=0){
		now+=dis(x-(*temp));
		now-=dis((*suf)-(*temp))+dis(x-(*suf));
		Set.erase(suf);
		suf=temp,temp=Suffix(suf);
	}
	return;
}
int main(){
	scanf("%d%d%d",&n,&x,&y);
	p[0]=point(0,0),p[1]=point(n,0),p[2]=point(x,y);
	now=dis(p[2]-p[0])+dis(p[2]-p[1]);
	for(int i=0;i<=2;++i){
		p[i].len=dis(p[i]);
		p[i].angle=atan2(p[i].y,p[i].x);
		Set.insert(p[i]);
	}
	scanf("%d",&m);
	for(int i=1;i<=m;++i){
		scanf("%lf%lf",&p[i].x,&p[i].y);
		p[i].len=dis(p[i]),p[i].angle=atan2(p[i].y,p[i].x);
	}
	scanf("%d",&q);
	for(int i=1;i<=q;++i){
		scanf("%d",&Q[i].op);
		if(Q[i].op==1)  scanf("%d",&Q[i].x),Del[Q[i].x]=true;
	}
	for(int i=1;i<=m;++i)
		if(!Del[i])  Insert(p[i]);
	int tot=0;
	for(int i=q;i>=1;--i){
		if(Q[i].op==2)  ans[++tot]=now;
		else  Insert(p[Q[i].x]);
	}
	for(int i=tot;i>=1;--i)  printf("%.2lf\n",ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值