luogu2521 [HAOI2011]防线修建

【题目】
  点击传送
【题解1】

  题意就是给你一大堆点,要你求出凸包,然后每次可以删除一个点,问你删除之后的周长是多少。

  对于30%,可以每删除一个点,就重新求凸包,复杂度O(N^2lbN)。

  对于100%,考虑当你删掉一个点时,那么这个点原来连接的左右两点一定还在凸包上,而凸包有变化的区域只限于这两点之间(以极角序为参考),所以你就可以在这个区间里进行局部的凸包重建。这个做法的复杂度很玄学,当数据极端的时候会被卡掉的,但是出题人显然没考虑到这一点,轻松AC了。

【题解2】

  听说这道题正解是,先读入所有操作,倒着看,就成了你要往凸包上插入一些点。然后用平衡树维护凸包,每次插入一个点,找到它按极角序应该在的位置,然后判断其左右会不会被覆盖(用叉积),用类似Graham的思想来做就好了。这样复杂度是O(NlbN)。网上大部分人都是用的set。

【代码(第一种算法)】

//凸包
#include <cstdio>
#include <cmath>
#include <algorithm>
#define maxn 100010
using namespace std;
struct vec{double x,y;int num;}p[maxn];
double C;
int N, top, left[maxn], right[maxn], s[maxn], table[maxn], on[maxn];
double cp(vec v1, vec v2){return v1.x*v2.y-v2.x*v1.y;}
bool cmp(vec p1, vec p2)
{
	double c=cp(p1,p2);
	return c==0?p1.x<p2.x or p1.y<p2.y:c>0;
}
void graham()
{
	int i;
	vec v1, v2;
	s[++top]=1;
	on[1]=true;
	sort(p+2,p+N+1,cmp);
	for(i=2;i<=N;i++)
	{
		v1=(vec){p[s[top]].x-p[s[top-1]].x,p[s[top]].y-p[s[top-1]].y,0};
		v2=(vec){p[i].x-p[s[top]].x,p[i].y-p[s[top]].y,0};
		while(top>1 and cp(v1,v2)<0)
		{
			on[s[top--]]=false;
			v1=(vec){p[s[top]].x-p[s[top-1]].x,p[s[top]].y-p[s[top-1]].y,0};
			v2=(vec){p[i].x-p[s[top]].x,p[i].y-p[s[top]].y,0};
		}
		right[i]=s[top];
		left[s[top]]=i;
		s[++top]=i;
		on[i]=true;
	}
	for(i=N-1;p[i].x*p[s[top]].y-p[i].y*p[s[top]].x==0;i--)
		right[i]=s[top],left[s[top]]=i,s[++top]=i;
	right[s[1]]=s[top];
	left[s[top]]=s[1];
}
double dist(vec p1, vec p2)
{
	double t1=p1.x-p2.x, t2=p1.y-p2.y;
	return sqrt(t1*t1+t2*t2);
}
void del(int num)
{
	int l=left[num], r=right[num], i, x;
	vec v1, v2;
	p[num].num=-1;
	if(!on[num])return;
	else on[num]=false;
	C-=dist(p[num],p[l])+dist(p[num],p[r]);
	s[top=1]=r;
	x=l==N?1:l+1;
	for(i=r+1;i!=x;i=i==N?1:i+1)
	{
		if(p[i].num==-1)continue;
		v1=(vec){p[s[top]].x-p[s[top-1]].x,p[s[top]].y-p[s[top-1]].y,0};
		v2=(vec){p[i].x-p[s[top]].x,p[i].y-p[s[top]].y,0};
		while(top>1 and cp(v1,v2)<0)
		{
			on[s[top--]]=false;
			v1=(vec){p[s[top]].x-p[s[top-1]].x,p[s[top]].y-p[s[top-1]].y,0};
			v2=(vec){p[i].x-p[s[top]].x,p[i].y-p[s[top]].y,0};
		}
		right[i]=s[top];
		left[s[top]]=i;
		s[++top]=i;
		on[i]=true;
	}
	for(i=2;i<=top;i++)C+=dist(p[s[i]],p[s[i-1]]);
}
int main()
{
	int x, y, q, i, a;
	scanf("%d%d%d",&a,&x,&y);
	p[1].x=p[1].y=0;
	p[2].x=a,p[2].y=0;
	p[3].x=x,p[3].y=y;
	scanf("%d",&N),N+=3;
	for(i=4;i<=N;i++)scanf("%lf%lf",&p[i].x,&p[i].y),p[i].num=i-3;
	graham();
	for(i=1;i<=N;i++)table[p[i].num]=i;
	for(C=dist(p[s[1]],p[s[top]]),i=2;i<=top;i++)C+=dist(p[s[i]],p[s[i-1]]);
	C-=a; 
	scanf("%d",&q);
	for(i=1;i<=q;i++)
	{
		scanf("%d",&x);
		if(x==1)scanf("%d",&y),del(table[y]);
		else printf("%.2lf\n",C);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值