bzoj 2300: [HAOI2011]防线修建(splay动态维护凸包)

2300: [HAOI2011]防线修建

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 800  Solved: 440
[ Submit][ Status][ Discuss]

Description

近来A国和B国的矛盾激化,为了预防不测,A国准备修建一条长长的防线,当然修建防线的话,肯定要把需要保护的城市修在防线内部了。可是A国上层现在还犹豫不决,到底该把哪些城市作为保护对象呢?又由于A国的经费有限,所以希望你能帮忙完成如下的一个任务:
1.给出你所有的A国城市坐标
2.A国上层经过讨论,考虑到经济问题,决定取消对i城市的保护,也就是说i城市不需要在防线内了
3.A国上层询问对于剩下要保护的城市,修建防线的总经费最少是多少
你需要对每次询问作出回答。注意单位1长度的防线花费为1。
A国的地形是这样的,形如下图,x轴是一条河流,相当于一条天然防线,不需要你再修建
A国总是有两个城市在河边,一个点是(0,0),一个点是(n,0),其余所有点的横坐标均大于0小于n,纵坐标均大于0。A国有一个不在(0,0)和(n,0)的首都。(0,0),(n,0)和首都这三个城市是一定需要保护的。

上图中,A,B,C,D,E点为A国城市,且目前都要保护,那么修建的防线就会是A-B-C-D,花费也就是线段AB的长度+线段BC的长度+线段CD的长度,如果,这个时候撤销B点的保护,那么防线变成下图


Input

第一行,三个整数n,x,y分别表示河边城市和首都是(0,0),(n,0),(x,y)。
第二行,一个整数m。
接下来m行,每行两个整数a,b表示A国的一个非首都非河边城市的坐标为(a,b)。
再接下来一个整数q,表示修改和询问总数。
接下来q行每行要么形如1 i,要么形如2,分别表示撤销第i个城市的保护和询问。

Output

对于每个询问输出1行,一个实数v,表示修建防线的花费,保留两位小数

Sample Input

4 2 1
2
1 2
3 2
5
2
1 1
2
1 2
2

Sample Output

6.47
5.84
4.47

HINT

m<=100000,q<=200000,n>1

所有点的坐标范围均在10000以内, 数据保证没有重点

Source

[ Submit][ Status][ Discuss]

题解:splay动态维护凸包。

因为删点不好操作,所以我们把数据离线,然后倒序处理,把删点操作变成加点操作。

用splay维护凸包中的点横坐标有序,插入一个点,如果(x,pre(x))的斜率小于(x,next(x))的斜率的话(注意特判横坐标相等的情况),说明形成的了一个凹陷的角,不满足凸包,直接删去刚插入的点即可。

否则,我们需要从这个点的左边找到第一条斜率(x,pre(x))<斜率(pre(x),pre(pre(x)))的边,不断的删去中间不满足的点,顺便更新答案。

从这个点的右边找到右边第一个斜率(x,next(x))>斜率(next(x),next(next(x)))的边,不断删除中间不满足的点。

需要注意的是在向左寻找的时候,如果两个点横坐标相同,斜率需要返回最大值,向右寻找的时候需要返回最小值。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 200003
#define inf 1000000000
using namespace std;
int fa[N],ch[N][3],xl[N],yl[N],n,m,pd[N],root;
double ans[N],ans1,ks[N],ks1[N];
struct point
{
	double x,y;
}p[N];
struct data
{
	int opt,x;
}a[N];
int get(int x)
{
	return ch[fa[x]][1]==x;
}
void clear(int x)
{
	fa[x]=ch[x][0]=ch[x][1]=0;
}
void rotate(int x)
{
	int y=fa[x]; int z=fa[y]; int which=get(x);
	ch[y][which]=ch[x][which^1]; fa[ch[x][which^1]]=y;
	ch[x][which^1]=y; fa[y]=x;
	if (z)  ch[z][ch[z][1]==y]=x;
	fa[x]=z;
}
void splay(int x,int tar)
{
	for (int f;(f=fa[x])!=tar;rotate(x))
	 if (fa[f]!=tar)
	  rotate(get(x)==get(f)?f:x);
	if (!tar)  root=x;
}
void solve(double x,double y,int k)
{
	if (!root)
	 {
	 	root=k; fa[k]=ch[k][0]=ch[k][1]=0;
	 	return;
	 }
	int now=root,f; 
	while (true)
	{
	   f=now;
	   now=ch[now][x>p[now].x];
	   if (!now)
	    {
	    	fa[k]=f; ch[f][x>p[f].x]=k;
	    	splay(k,0); return;
		}
	}
}
int pre(int now)
{
	now=ch[now][0];
	while (ch[now][1]) now=ch[now][1];
	return now;
}
int next(int now)
{
    now=ch[now][1];
	while (ch[now][0]) now=ch[now][0];
	return now;
}
void del(int now)
{
	if (!ch[now][0]&&!ch[now][1])  
     {  
        clear(now); root=0; return;  
     }  
    if (!ch[now][0])  
     {  
        int ordr=now; now=ch[now][1]; fa[now]=0; clear(ordr); return;  
     }  
    if (!ch[now][1])  
     {  
        int ordr=now; now=ch[now][0]; fa[now]=0; clear(ordr); return;  
     }  
    int t=pre(now),oldr=root;  
    splay(t,0);  fa[ch[oldr][1]]=root;  ch[root][1]=ch[oldr][1]; clear(oldr);    
}
double getdis(int x,int y)
{
    int t=p[x].x-p[y].x;
    int t1=p[y].y-p[x].y;
    return sqrt(t*t+t1*t1);
}
double getk(int x,int y,int opt)
{
	if (p[x].x==p[y].x)  
	 if (opt==1) return inf;
	 else return -inf;
	return (p[x].y-p[y].y)/(p[x].x-p[y].x); 
}
void insert(double x,double y,int k)
{
	solve(x,y,k);
	int l=pre(k); int r=next(k);
	if (!l||!r)  return;
	splay(l,k); splay(r,k);
	if(p[l].x!=p[k].x&&p[k].x!=p[r].x&&getk(l,k,1)<=getk(k,r,1)||(p[k].x==p[l].x&&p[k].y<=p[l].y)||(p[k].x==p[r].x&&p[k].y<=p[r].y))
	 {
	 	del(k);
	 	return;
	 }
	ans1-=getdis(l,r);
	ks[k]=getk(l,k,1);  ks[r]=getk(k,r,1);
	ks1[k]=getk(k,r,2); ks1[l]=getk(l,k,2);
	ans1+=getdis(l,k); ans1+=getdis(k,r);
	int now=k;
	while(ks[now]>=ks[l]&&l!=1)
	 {
	 	ans1-=getdis(now,l); splay(l,0); ans1-=getdis(l,pre(l));
	 	del(l); splay(now,0); l=pre(now);
	 	//splay(l,now);
	 	ks[now]=getk(l,now,1);
	 	ks1[l]=getk(l,now,2);
	 	ans1+=getdis(l,now);
	 }
	while (ks1[now]<=ks1[r]&&r!=2)
	{
		ans1-=getdis(now,r); splay(r,0); ans1-=getdis(r,next(r));
		del(r); splay(now,0); r=next(now);
		//splay(r,now);
		ks1[now]=getk(now,r,2);
		ks[r]=getk(now,r,1);
		ans1+=getdis(now,r);
	}
}
int main()
{
   	freopen("a.in","r",stdin);
   	freopen("my.out","w",stdout);
   	int x1,y1;
   	scanf("%d%d%d",&n,&x1,&y1);
   	p[1].x=0; p[1].y=0; p[2].x=n; p[2].y=0; p[3].x=x1; p[3].y=y1; 
   	insert(0,0,1); 
   	insert(n,0,2);  ans1+=getdis(1,2); ks[1]=inf; ks1[2]=-inf;
   	insert(x1,y1,3); 
   	scanf("%d",&m);
   	for (int i=4;i<=m+3;i++)
   	 scanf("%lf%lf",&p[i].x,&p[i].y);
   	int t; scanf("%d",&t);
   	for (int i=1;i<=t;i++)
   	 {
   	 	scanf("%d",&a[i].opt);
   	 	if (a[i].opt==1)  scanf("%d",&a[i].x),a[i].x+=3,pd[a[i].x]=1;
	 }
	for (int i=4;i<=m+3;i++)
	 if (!pd[i])  insert(p[i].x,p[i].y,i);
	int cnt=0;
	for (int i=t;i>=1;i--)
	 {
	 	if (a[i].opt==1)  {
	 		int k=a[i].x;
	 		insert(p[k].x,p[k].y,k);
		 }
		else ans[++cnt]=ans1;
	 }
	for (int i=cnt;i>=1;i--)
	 printf("%0.2lf\n",ans[i]);
} 



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值