凸包习题总结

在平面上能包含所有给定点的最小凸多边形叫做凸包

一般的题目通常只会让你维护上凸壳或下凸壳

斜率优化DP是最常考察的题型

一、P2742 [USACO5.1]圈奶牛Fencing the Cows /【模板】二维凸包

题目传送门

分析

凸包的模板题

首先我们找出纵坐标最小的点,如果有多个点的纵坐标都是最小的则取其中横坐标最小的

最后找到的这个点一定在我们要维护的凸包上

对于剩下的点,按照它与选出的点形成的直线的极角从小到大排序

极角是从 \(x\) 轴正半轴逆时针旋转到向量方向所需要的角度,向量 \((x,y)\) 的极角为 \(atan2(y,x)\),弧度制

在这道题也可以用叉积判断

叉积的公式是 \(u \times v=x_u \times y_v−x_v \times y_u=−(v \times u)\)

\(a×b=0\)\(a\)\(b\) 共线(可以反向)

\(a×b>0\)\(b\)\(a\) 逆时针方向

\(a×b<0\)\(b\)\(a\) 顺时针方向

排好序后从前向后扫

如果即将入栈的元素与栈顶两个元素所构成了一个类似于凹壳的东西

那么显然处于顶点的那个点一定不在这个点集的凸包上,所以把它弹出栈,新点入栈

代码

#include<cstdio>
#include<cmath>
#include<algorithm>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e5+5;
const double eps=1e-10;
struct Node{
	double x,y;
	Node(){}
	Node(rg double aa,rg double bb){
		x=aa,y=bb;
	}
	friend Node operator -(const Node& A,const Node& B){
		return Node(A.x-B.x,A.y-B.y);
	}
	friend double operator ^(const Node& A,const Node& B){
		return A.x*B.y-B.x*A.y;
	}
}p[maxn],sta[maxn];
int n,tp;
double getdis(rg Node aa,rg Node bb){
	return sqrt((aa.x-bb.x)*(aa.x-bb.x)+(aa.y-bb.y)*(aa.y-bb.y));
}
bool cmp(rg Node aa,rg Node bb){
	rg double nans=(aa-p[1])^(bb-p[1]);
	if(nans>eps) return 1;
	else if(std::fabs(nans)<=eps) return getdis(aa,p[1])<getdis(bb,p[1]);
	else return 0;
}
int main(){
	n=read();
	for(rg int i=1;i<=n;i++){
		scanf("%lf%lf",&p[i].x,&p[i].y);
		if(p[i].y<p[1].y || (p[i].y==p[1].y && p[i].x<p[1].x)) std::swap(p[1],p[i]);
	}
	std::sort(p+2,p+1+n,cmp);
	for(rg int i=1;i<=n;i++){
		while(tp>1 && ((p[i]-sta[tp])^(sta[tp]-sta[tp-1]))>eps) tp--;
		sta[++tp]=p[i];
	}
	sta[tp+1]=sta[1];
	double nans=0;
	for(rg int i=1;i<=tp;i++){
		nans+=getdis(sta[i],sta[i+1]);
	}
	printf("%.2f\n",nans);
	return 0;
}

二、P1452 [USACO03FALL]Beauty Contest G /【模板】旋转卡壳

题目传送门

分析

引用yyb巨佬的博客

大致总结一下旋转卡壳的步骤:

第一步:求出点的凸包

第二步:从最靠近左下方的点开始逆时针依次枚举所有的边(求凸包的时候已经记录在一个栈里面了)

第三部:继承上一次寻找时的结果,继续逆时针寻找最远的点(这里用面积来判断是否更远)

代码

#include<cstdio>
#include<cmath>
#include<algorithm>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e6+5;
const double eps=1e-10;
struct Node{
	double x,y;
	Node(){}
	Node(rg double aa,rg double bb){
		x=aa,y=bb;
	}
	friend Node operator -(const Node& A,const Node& B){
		return Node(A.x-B.x,A.y-B.y);
	}
	friend double operator ^(const Node& A,const Node& B){
		return A.x*B.y-B.x*A.y;
	}
}p[maxn],sta[maxn];
int n,tp;
double getdis(rg Node aa,rg Node bb){
	return (aa.x-bb.x)*(aa.x-bb.x)+(aa.y-bb.y)*(aa.y-bb.y);
}
bool cmp(rg Node aa,rg Node bb){
	rg double nans=(aa-p[1])^(bb-p[1]);
	if(nans>eps) return 1;
	else if(std::fabs(nans)<=eps) return getdis(aa,p[1])<getdis(bb,p[1]);
	else return 0;
}
int main(){
	n=read();
	for(rg int i=1;i<=n;i++){
		scanf("%lf%lf",&p[i].x,&p[i].y);
		if(p[i].y<p[1].y || (p[i].y==p[1].y && p[i].x<p[1].x)) std::swap(p[1],p[i]);
	}
	std::sort(p+2,p+1+n,cmp);
	for(rg int i=1;i<=n;i++){
		while(tp>1 && ((p[i]-sta[tp])^(sta[tp]-sta[tp-1]))>eps) tp--;
		sta[++tp]=p[i];
	}
	sta[tp+1]=sta[1];
	rg double nans=0;
	for(rg int i=1,j=1;i<=tp;i++){
		while(((sta[j]-sta[i])^(sta[j]-sta[i+1]))<((sta[j+1]-sta[i])^(sta[j+1]-sta[i+1]))) j=j+1>tp?j+1-tp:j+1;
		nans=std::max(nans,std::max(getdis(sta[i],sta[j]),getdis(sta[i+1],sta[j])));
	}
	printf("%.0f\n",nans);
	return 0;
}

三、陶陶的难题II

题目传送门

分析

\(\frac{y_i+q_j}{x_i+p_j}\) 的最大值

很显然的一个 \(01\) 分数规划的问题

\(\frac{y_i+q_j}{x_i+p_j} \geq mids\)

因为都是正数,所以可以直接把分母乘过去

\((x_i+p_j)mids \leq (y_i+q_j)\)

化简后可以得到 \(y_i-x_imids+q_i-p_imids \geq 0\)

左半部分和右半部分的形式其实是一样的

问题就转化成了如果求 \(y_i-x_imids\) 的最大值

\(y_i-x_imids=ans\)

\(y_i=x_imids+ans\)

本质上就是把平面中若干个坐标为 \((x,y)\) 的点带入一条斜率为 \(mids\) 的直线中,求截距的最大值

所以维护上凸壳,每次查询时在凸壳上查找第一个斜率小于 \(mids\) 的位置

该位置的点截距是最大的

如果用斜率维护凸壳,要特判斜率不存在的情况

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=6e4+5;
typedef double db;
const db eps=1e-7;
int h[maxn],tot=1,n,m;
struct asd{
	int to,nxt;
}b[maxn];
void ad(rg int aa,rg int bb){
	b[tot].to=bb;
	b[tot].nxt=h[aa];
	h[aa]=tot++;
}
db x[maxn],y[maxn],p[maxn],q[maxn];
int son[maxn],siz[maxn],fa[maxn],dep[maxn];
void dfs1(rg int now,rg int lat){
	fa[now]=lat;
	siz[now]=1;
	dep[now]=dep[lat]+1;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==lat) continue;
		dfs1(u,now);
		siz[now]+=siz[u];
		if(son[now]==0 || siz[u]>siz[son[now]]) son[now]=u;
	}
}
int tp[maxn],dfn[maxn],dfnc,rk[maxn];
void dfs2(rg int now,rg int top){
	tp[now]=top;
	dfn[now]=++dfnc;
	rk[dfnc]=now;
	if(son[now]) dfs2(son[now],top);
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==fa[now] || u==son[now]) continue;
		dfs2(u,u);
	}
}
struct Node{
	double x,y;
	Node(){}
	Node(rg double aa,rg double bb){
		x=aa,y=bb;
	}
};
struct trr{
	int l,r;
	std::vector<Node> ans1,ans2;
}tr[maxn<<2];
Node sta[maxn],que[maxn];
int ncnt=0,head,tail;
double xl(rg Node aa,rg Node bb){
	if(std::fabs(bb.x-aa.x)<eps){
		if(std::fabs(bb.y-aa.y)<eps) return 0;
		else if(bb.y>aa.y) return 1e8;
		else return -1e8;
	}
	return (bb.y-aa.y)/(bb.x-aa.x);
}
void push_up1(rg int da){
	rg int cnt0=0,cnt1=0,tot0=tr[da<<1].ans1.size(),tot1=tr[da<<1|1].ans1.size();
	ncnt=0;
	while(cnt0<tot0 || cnt1<tot1){
		if(cnt0==tot0) sta[++ncnt]=tr[da<<1|1].ans1[cnt1++];
		else if(cnt1==tot1) sta[++ncnt]=tr[da<<1].ans1[cnt0++];
		else if(tr[da<<1].ans1[cnt0].x<tr[da<<1|1].ans1[cnt1].x || (tr[da<<1].ans1[cnt0].x==tr[da<<1|1].ans1[cnt1].x && tr[da<<1].ans1[cnt0].y<tr[da<<1|1].ans1[cnt1].y)) sta[++ncnt]=tr[da<<1].ans1[cnt0++];
		else sta[++ncnt]=tr[da<<1|1].ans1[cnt1++];
	}
	head=1,tail=0;
	for(rg int i=1;i<=ncnt;i++){
		while(tail>1 && xl(que[tail],sta[i])>xl(que[tail-1],que[tail])) tail--;
		que[++tail]=sta[i];
	}
	for(rg int i=head;i<=tail;i++) tr[da].ans1.push_back(que[i]);
}
void push_up2(rg int da){
	rg int cnt0=0,cnt1=0,tot0=tr[da<<1].ans2.size(),tot1=tr[da<<1|1].ans2.size();
	ncnt=0;
	while(cnt0<tot0 || cnt1<tot1){
		if(cnt0==tot0) sta[++ncnt]=tr[da<<1|1].ans2[cnt1++];
		else if(cnt1==tot1) sta[++ncnt]=tr[da<<1].ans2[cnt0++];
		else if(tr[da<<1].ans2[cnt0].x<tr[da<<1|1].ans2[cnt1].x || (tr[da<<1].ans2[cnt0].x==tr[da<<1|1].ans2[cnt1].x && tr[da<<1].ans2[cnt0].y<tr[da<<1|1].ans2[cnt1].y)) sta[++ncnt]=tr[da<<1].ans2[cnt0++];
		else sta[++ncnt]=tr[da<<1|1].ans2[cnt1++];
	}
	head=1,tail=0;
	for(rg int i=1;i<=ncnt;i++){
		while(tail>1 && xl(que[tail],sta[i])>xl(que[tail-1],que[tail])) tail--;
		que[++tail]=sta[i];
	}
	for(rg int i=head;i<=tail;i++) tr[da].ans2.push_back(que[i]);
}
void build(rg int da,rg int l,rg int r){
	tr[da].l=l,tr[da].r=r;
	if(tr[da].l==tr[da].r){
		tr[da].ans1.push_back(Node(x[rk[l]],y[rk[l]]));
		tr[da].ans2.push_back(Node(p[rk[l]],q[rk[l]]));
		return;
	}
	rg int mids=(l+r)>>1;
	build(da<<1,l,mids);
	build(da<<1|1,mids+1,r);
	push_up1(da);
	push_up2(da);
}
double getans1(rg int da,rg double nx){
	rg int l=1,r=tr[da].ans1.size(),mids;
	while(l<r){
		mids=(l+r)>>1;
		if(xl(tr[da].ans1[mids-1],tr[da].ans1[mids])<=nx) r=mids;
		else l=mids+1;
	}
	l--;
	return tr[da].ans1[l].y-tr[da].ans1[l].x*nx;
}
double cx1(rg int da,rg int l,rg int r,rg double nx){
	if(tr[da].l>=l && tr[da].r<=r){
		return getans1(da,nx);
	}
	rg int mids=(tr[da].l+tr[da].r)>>1;
	rg double nans=-1e8;
	if(l<=mids) nans=std::max(nans,cx1(da<<1,l,r,nx));
	if(r>mids) nans=std::max(nans,cx1(da<<1|1,l,r,nx));
	return nans;
}
double getans2(rg int da,rg double nx){
	rg int l=1,r=tr[da].ans2.size(),mids;
	while(l<r){
		mids=(l+r)>>1;
		if(xl(tr[da].ans2[mids-1],tr[da].ans2[mids])<=nx) r=mids;
		else l=mids+1;
	}
	l--;
	return tr[da].ans2[l].y-tr[da].ans2[l].x*nx;
}
double cx2(rg int da,rg int l,rg int r,rg double nx){
	if(tr[da].l>=l && tr[da].r<=r){
		return getans2(da,nx);
	}
	rg int mids=(tr[da].l+tr[da].r)>>1;
	rg double nans=-1e8;
	if(l<=mids) nans=std::max(nans,cx2(da<<1,l,r,nx));
	if(r>mids) nans=std::max(nans,cx2(da<<1|1,l,r,nx));
	return nans;
}
bool trcx(rg int xx,rg int yy,rg double nx){
	rg double nans1=-1e8,nans2=-1e8;
	while(tp[xx]!=tp[yy]){
		if(dep[tp[xx]]<dep[tp[yy]]) std::swap(xx,yy);
		nans1=std::max(nans1,cx1(1,dfn[tp[xx]],dfn[xx],nx));
		nans2=std::max(nans2,cx2(1,dfn[tp[xx]],dfn[xx],nx));
		xx=fa[tp[xx]];
	}
	if(dep[xx]<dep[yy]) std::swap(xx,yy);
	nans1=std::max(nans1,cx1(1,dfn[yy],dfn[xx],nx));
	nans2=std::max(nans2,cx2(1,dfn[yy],dfn[xx],nx));
	return nans1+nans2>=eps;
}
double solve(rg int xx,rg int yy){
	rg double l=-1e8,r=1e8,mids;
	for(rg int i=1;i<=40;i++){
		mids=(l+r)/2.0;
		if(trcx(xx,yy,mids)) l=mids;
		else r=mids;
	}
	return mids;
}
int main(){
	memset(h,-1,sizeof(h));
	n=read();
	for(rg int i=1;i<=n;i++) scanf("%lf",&x[i]);
	for(rg int i=1;i<=n;i++) scanf("%lf",&y[i]);
	for(rg int i=1;i<=n;i++) scanf("%lf",&p[i]);
	for(rg int i=1;i<=n;i++) scanf("%lf",&q[i]);
	rg int aa,bb;
	for(rg int i=2;i<=n;i++){
		aa=read(),bb=read();
		ad(aa,bb);
		ad(bb,aa);
	}
	dfs1(1,0);
	dfs2(1,1);
	build(1,1,n);
	m=read();
	for(rg int i=1;i<=m;i++){
		aa=read(),bb=read();
		printf("%.4f\n",solve(aa,bb));
	}
	return 0;
}

四、P4192 旅行规划

题目传送门

分析

转化一下题意

区间加等差数列,查询区间最大值

如果是单点查询的话,可以在线段树上打一个标记,边查询边下放

但是区间查询的话需要整棵树下放标记,复杂度太高

考虑万能的分块做法

因为等差数列加等差数列还是还是一个等差数列

所以对每一个块维护等差数列的首项 \(beg\) 以及公差 \(d\)

这样,一个块内所有点的值都可以写成与下标相关的一次函数的形式

\(ans[i]=i \times d+sum[i]\)

\(sum[i]=ans[i]-i \times d\)

其中 \(sum[i]\) 为上一次重构后 \(i\) 处前缀和的值

对于整个块的首项,我们先不去考虑,最后把它加上即可

这样,我们就可以把下标 \(i\) 看成横坐标,把 \(sum[i]\) 看成纵坐标,把 \(-d\) 看成斜率

如果是对整个块进行修改,那么横纵坐标都是不变的

变化的只是斜率

因此可以维护一个上凸壳

在上凸壳中二分查找使截距 \(ans[i]\) 最大的点

如果是对块的一部分进行修改或查询,我们就把这个块进行暴力重构,重新建一个凸包

如果块长是 \(\sqrt{n}\) 的话

复杂度就是 \(m\sqrt{n}log\sqrt{n}\)

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e5+5;
typedef double db;
const db eps=1e-18;
struct Node{
	int x;
	long long y;
	Node(){}
	Node(rg int aa,rg long long bb){
		x=aa,y=bb;
	}
	friend bool operator < (const Node& A,const Node& B){
		if(A.x==B.x) return A.y<B.y;
		else return A.x<B.x;
	}
};
int shuyu[maxn],blo,n,m,l[maxn],r[maxn],tp,tail;
std::vector<Node> ans[maxn];
Node sta[maxn],que[maxn];
long long beg[maxn],sum[maxn],d[maxn];
inline double xl(rg Node aa,rg Node bb){
	if(std::fabs((double)bb.x-(double)aa.x)<eps){
		if(std::fabs((double)bb.y-(double)aa.y)<eps) return 0;
		else if(bb.y>aa.y) return 1e18;
		else return -1e18;
	}
	return ((double)bb.y-(double)aa.y)/((double)bb.x-(double)aa.x);
}
void build(rg int id){
	if(d[id]){
		for(rg int i=l[id];i<=r[id];i++) sum[i]+=1LL*d[id]*(i-l[id]+1);
		d[id]=0;
	}
	if(beg[id]){
		for(rg int i=l[id];i<=r[id];i++) sum[i]+=beg[id];
		beg[id]=0;
	}
	tp=tail=0;
	ans[id].clear();
	for(rg int i=l[id];i<=r[id];i++) sta[++tp]=Node(i-l[id]+1,sum[i]);
	std::sort(sta+1,sta+tp+1);
	for(rg int i=1;i<=tp;i++){
		while(tail>1 && xl(que[tail],sta[i])>=xl(que[tail-1],que[tail])) tail--;
		que[++tail]=sta[i];
	}
	for(rg int i=1;i<=tail;i++) ans[id].push_back(que[i]);
}
void xg(rg int nl,rg int nr,rg int val){
	for(rg int i=nl;i<=std::min(r[shuyu[nl]],nr);i++){
		sum[i]+=1LL*val*(i-nl+1);	
	}
	for(rg int i=nr+1;i<=r[shuyu[nr]];i++){
		sum[i]+=1LL*val*(nr-nl+1);
	}
	for(rg int i=shuyu[nr]+1;i<=shuyu[n];i++){
		beg[i]+=1LL*val*(nr-nl+1);
	}
	build(shuyu[nl]);
	if(shuyu[nl]==shuyu[nr]) return;
	for(rg int i=l[shuyu[nr]];i<=nr;i++){
		sum[i]+=1LL*val*(i-nl+1);
	}
	build(shuyu[nr]);
	for(rg int i=shuyu[nl]+1;i<=shuyu[nr]-1;i++){
		beg[i]+=1LL*(l[i]-nl)*val;
		d[i]+=val;
	}
}
long long qjcx(rg int id){
	rg int nl=1,nr=ans[id].size(),mids;
	rg double nans=-1.0*d[id];
	while(nl<nr){
		mids=(nl+nr)>>1;
		if(xl(ans[id][mids-1],ans[id][mids])<=nans) nr=mids;
		else nl=mids+1;
	}
	nl--;
	return (long long)ans[id][nl].y+(long long)d[id]*ans[id][nl].x+(long long)beg[id];
}
long long cx(rg int nl,rg int nr){
	build(shuyu[nl]);
	rg long long nans=-0x3f3f3f3f3f3f3f3f;
	for(rg int i=nl;i<=std::min(r[shuyu[nl]],nr);i++) nans=std::max(nans,sum[i]);
	if(shuyu[nl]==shuyu[nr]) return nans;
	build(shuyu[nr]);
	for(rg int i=l[shuyu[nr]];i<=nr;i++) nans=std::max(nans,sum[i]);
	for(rg int i=shuyu[nl]+1;i<=shuyu[nr]-1;i++){
		nans=std::max(nans,qjcx(i));
	}
	return nans;
}
int main(){
	memset(l,0x3f,sizeof(l));
	n=read();
	blo=sqrt(n);
	for(rg int i=1;i<=n;i++) shuyu[i]=(i-1)/blo+1;
	for(rg int i=1;i<=n;i++) sum[i]=read();
	for(rg int i=1;i<=n;i++) sum[i]+=sum[i-1];
	for(rg int i=1;i<=n;i++){
		l[shuyu[i]]=std::min(l[shuyu[i]],i);
		r[shuyu[i]]=std::max(r[shuyu[i]],i);
	}
	for(rg int i=1;i<=shuyu[n];i++) build(i);
	m=read();
	rg int aa,bb,cc,dd;
	for(rg int i=1;i<=m;i++){
		aa=read(),bb=read(),cc=read();
		if(aa==0){
			dd=read();
			xg(bb,cc,dd);
		} else {
			printf("%lld\n",cx(bb,cc));
		}
	}
	return 0;
}

五、P3309 [SDOI2014]向量集

题目传送门

分析

\(ax+by\) 的最大值

如果没有加点操作,就是线段树上维护凸包的裸题

\(ans=ax+by\)

\(by=ans-ax\)

\(y=-\frac{a}{b}x+\frac{ans}{b}\)

\(b<0\) 就是求截距的最小值,维护一个下凸壳即可

\(b>0\) 则是求截距的最大值,维护一个上凸壳即可

对于加点操作,我们记录一下线段树每个区间所代表的节点已经加入了多少向量

如果向量的个数和区间的长度相等,就构建凸包

因为每一个节点只会构建一次凸包,所以复杂度是正确的

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=4e5+5;
typedef long double db;
const db eps=1e-7;
int n;
char s[maxn];
db x[maxn],y[maxn];
struct Node{
	double x,y;
	Node(){}
	Node(rg double aa,rg double bb){
		x=aa,y=bb;
	}
};
struct trr{
	int l,r,siz;
	std::vector<Node> ans1,ans2;
}tr[maxn<<2];
Node sta[maxn],que[maxn];
int ncnt=0,head,tail;
double xl(rg Node aa,rg Node bb){
	if(std::fabs(bb.x-aa.x)<eps){
		if(std::fabs(bb.y-aa.y)<eps) return 0;
		else if(bb.y>aa.y) return 1e18;
		else return -1e18;
	}
	return (bb.y-aa.y)/(bb.x-aa.x);
}
void push_up1(rg int da){
	rg int cnt0=0,cnt1=0,tot0=tr[da<<1].ans1.size(),tot1=tr[da<<1|1].ans1.size();
	ncnt=0;
	while(cnt0<tot0 || cnt1<tot1){
		if(cnt0==tot0) sta[++ncnt]=tr[da<<1|1].ans1[cnt1++];
		else if(cnt1==tot1) sta[++ncnt]=tr[da<<1].ans1[cnt0++];
		else if(tr[da<<1].ans1[cnt0].x<tr[da<<1|1].ans1[cnt1].x || (tr[da<<1].ans1[cnt0].x==tr[da<<1|1].ans1[cnt1].x && tr[da<<1].ans1[cnt0].y<tr[da<<1|1].ans1[cnt1].y)) sta[++ncnt]=tr[da<<1].ans1[cnt0++];
		else sta[++ncnt]=tr[da<<1|1].ans1[cnt1++];
	}
	head=1,tail=0;
	for(rg int i=1;i<=ncnt;i++){
		while(tail>1 && xl(que[tail],sta[i])>=xl(que[tail-1],que[tail])) tail--;
		que[++tail]=sta[i];
	}
	for(rg int i=head;i<=tail;i++) tr[da].ans1.push_back(que[i]);
}
void push_up2(rg int da){
	rg int cnt0=0,cnt1=0,tot0=tr[da<<1].ans2.size(),tot1=tr[da<<1|1].ans2.size();
	ncnt=0;
	while(cnt0<tot0 || cnt1<tot1){
		if(cnt0==tot0) sta[++ncnt]=tr[da<<1|1].ans2[cnt1++];
		else if(cnt1==tot1) sta[++ncnt]=tr[da<<1].ans2[cnt0++];
		else if(tr[da<<1].ans2[cnt0].x<tr[da<<1|1].ans2[cnt1].x || (tr[da<<1].ans2[cnt0].x==tr[da<<1|1].ans2[cnt1].x && tr[da<<1].ans2[cnt0].y<tr[da<<1|1].ans2[cnt1].y)) sta[++ncnt]=tr[da<<1].ans2[cnt0++];
		else sta[++ncnt]=tr[da<<1|1].ans2[cnt1++];
	}
	head=1,tail=0;
	for(rg int i=1;i<=ncnt;i++){
		while(tail>1 && xl(que[tail],sta[i])<=xl(que[tail-1],que[tail])) tail--;
		que[++tail]=sta[i];
	}
	for(rg int i=head;i<=tail;i++) tr[da].ans2.push_back(que[i]);
}
void build(rg int da,rg int l,rg int r){
	tr[da].l=l,tr[da].r=r;
	if(tr[da].l==tr[da].r) return;
	rg int mids=(l+r)>>1;
	build(da<<1,l,mids);
	build(da<<1|1,mids+1,r);
}
void updat(rg int da,rg int wz,rg double x,rg double y){
	if(tr[da].l==tr[da].r){
		tr[da].ans1.push_back(Node(x,y));
		tr[da].ans2.push_back(Node(x,y));
		tr[da].siz++;
		return;
	}
	rg int mids=(tr[da].l+tr[da].r)>>1;
	if(wz<=mids) updat(da<<1,wz,x,y);
	else updat(da<<1|1,wz,x,y);
	tr[da].siz++;
	if(tr[da].siz==tr[da].r-tr[da].l+1){
		push_up1(da);
		push_up2(da);
	}
}
int aa,bb,cc,dd;
long long getans1(rg int da,rg double nx){
	rg int l=1,r=tr[da].ans1.size(),mids;
	while(l<r){
		mids=(l+r)>>1;
		if(xl(tr[da].ans1[mids-1],tr[da].ans1[mids])<=nx) r=mids;
		else l=mids+1;
	}
	l--;
	return 1LL*tr[da].ans1[l].x*cc+1LL*tr[da].ans1[l].y*dd;
}
long long cx1(rg int da,rg int l,rg int r,rg double nx){
	if(tr[da].l>=l && tr[da].r<=r){
		return getans1(da,nx);
	}
	rg int mids=(tr[da].l+tr[da].r)>>1;
	rg long long nans=-0x7f7f7f7f7f7f7f7f;
	if(l<=mids) nans=std::max(nans,cx1(da<<1,l,r,nx));
	if(r>mids) nans=std::max(nans,cx1(da<<1|1,l,r,nx));
	return nans;
}
long long getans2(rg int da,rg double nx){
	rg int l=1,r=tr[da].ans2.size(),mids;
	while(l<r){
		mids=(l+r)>>1;
		if(xl(tr[da].ans2[mids-1],tr[da].ans2[mids])<=nx) l=mids+1;
		else r=mids;
	}
	l--;
	return 1LL*tr[da].ans2[l].x*cc+1LL*tr[da].ans2[l].y*dd;
}
long long cx2(rg int da,rg int l,rg int r,rg double nx){
	if(tr[da].l>=l && tr[da].r<=r){
		return getans2(da,nx);
	}
	rg int mids=(tr[da].l+tr[da].r)>>1;
	rg long long nans=-0x7f7f7f7f7f7f7f7f;
	if(l<=mids) nans=std::max(nans,cx2(da<<1,l,r,nx));
	if(r>mids) nans=std::max(nans,cx2(da<<1|1,l,r,nx));
	return nans;
}
bool jud=0;
int cnt;
long long latans;
int get(rg int now){
	return now^(latans&0x7fffffff);
}
int main(){
	n=read();
	scanf("%s",s+1);
	build(1,1,n);
	if(s[1]!='E') jud=1;
	for(rg int i=1;i<=n;i++){
		scanf("%s",s+1);
		if(s[1]=='A'){
			aa=read(),bb=read();
			if(jud) aa=get(aa),bb=get(bb);
			updat(1,++cnt,aa,bb);
		} else {
			cc=read(),dd=read(),aa=read(),bb=read();
			if(jud) aa=get(aa),bb=get(bb),cc=get(cc),dd=get(dd);
			if(dd<0) latans=cx2(1,aa,bb,-(db)cc/dd);
			else latans=cx1(1,aa,bb,-(db)cc/dd);
			printf("%lld\n",latans);
		}
	}
	return 0;
}

六、P2521 [HAOI2011]防线修建

题目传送门

分析

动态维护凸包

因为题目中只有删点操作而且没有强制在线

所以可以倒序处理,将删点操作转变为加点操作

加点时,在平衡树中找到该点横坐标的前驱后继

把不合法的在平衡树中删掉即可

代码

#include<cstdio>
#include<algorithm>
#include<set>
#include<cmath>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=2e5+5;
const double eps=1e-8;
struct Node{
	double x,y;
	Node(){}
	Node(rg double aa,rg double bb){
		x=aa,y=bb;
	}
	friend bool operator < (rg const Node& A,rg const Node& B){
		if(A.x==B.x) return A.y<B.y;
		return A.x<B.x;
	}
}b[maxn],cp;
int n,m,q,jlx[maxn],jly[maxn];
double ans[maxn],nans;
bool vis[maxn];
std::set<Node> s;
#define sit std::set<Node>::iterator
double getdis(rg Node aa,rg Node bb){
	return sqrt((aa.x-bb.x)*(aa.x-bb.x)+(aa.y-bb.y)*(aa.y-bb.y));
}
double xl(rg Node aa,rg Node bb){
	if(std::fabs(bb.x-aa.x)<eps){
		if(std::fabs(bb.y-aa.y)<eps) return 0;
		else if(bb.y>aa.y) return 1e8;
		else return -1e8;
	}
	return (bb.y-aa.y)/(bb.x-aa.x);
}
void ad(rg Node now){
	rg sit itl,itr=s.lower_bound(now),tmp;
	itl=itr;
	--itl;
	if(xl(now,*itr)>=xl(*itl,now)) return;
	nans-=getdis(*itl,*itr);
	while(itr!=s.end()){
		tmp=itr;
		++itr;
		if(itr==s.end() || xl(*tmp,*itr)<xl(now,*tmp)) break;
		nans-=getdis(*itr,*tmp);
		s.erase(tmp);
	}
	while(itl!=s.begin()){
		tmp=itl;
		--itl;
		if(xl(*tmp,now)<xl(*itl,*tmp)) break;
		nans-=getdis(*itl,*tmp);
		s.erase(tmp);
	}
	s.insert(now);
	itl=itr=s.lower_bound(now);
	--itl,++itr;
	nans+=getdis(*itl,now);
	nans+=getdis(*itr,now);
}
int main(){
	n=read(),cp.x=read(),cp.y=read();
	m=read();
	for(rg int i=1;i<=m;i++){
		b[i].x=read(),b[i].y=read();
	}
	q=read();
	for(rg int i=1;i<=q;i++){
		jlx[i]=read();
		if(jlx[i]==1){
			jly[i]=read();
			vis[jly[i]]=1;
		}
	}
	s.insert(Node(0,0));
	s.insert(Node(n,0));
	s.insert(cp);
	nans=getdis(Node(0,0),cp)+getdis(Node(n,0),cp);
	for(rg int i=1;i<=m;i++){
		if(!vis[i]) ad(b[i]);
	}
	for(rg int i=q;i>=1;i--){
		if(jlx[i]==2) ans[i]=nans;
		else ad(b[jly[i]]);
	}
	for(rg int i=1;i<=q;i++){
		if(jlx[i]==2) printf("%.2f\n",ans[i]);
	}
	return 0;
}

七、P4027 [NOI2007] 货币兑换

题目传送门

分析

如果我们在第 \(i\) 天买入纪念券第 \(j\) 天卖出纪念券有利可图

那么我们一定会在第 \(i\) 天花完所有的钱

因此可以求出在第 \(i\) 天花完钱在第 \(j\) 天得到的两种金券数 \(x_j=\frac{f_jR_j}{a_jR_j+b_j}\)\(y_j=\frac{f_j}{a_jR_j+b_j}\)

然后得到状态转移程:\(f_i=x_ja_i+y_jb_i\)

将方程稍微变形成直线斜截式方程

\(y_j=-\frac{a_ix_j}{b_i}+\frac{f_i}{b_i}\)

一个可以进行斜率优化的式子

但是我们会发现这道题的横坐标和斜率不是单调递增的

所以要用 \(CDQ\) 分治解决

对于左半部分按照横坐标 \(x\) 从小到大排序

对于右半部分按照斜率从小到大排序

先处理左半部分的 \(dp\)

用得到的结果去更新右半部分的 \(dp\)

然后再递归右半部分

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#define rg register
const int maxn=1e5+5;
const double eps=1e-8;
int n,s;
struct asd{
	double a,b,rat,k,x,y;
	int id;
}q[maxn],tmp[maxn];
double f[maxn];
double xl(rg int i,rg int j){
	if(std::fabs(q[j].x-q[i].x)<eps){
		if(std::fabs(q[j].y-q[i].y)<eps) return 0;
		else if(q[j].y>q[i].y) return 1e18;
		else return -1e18;
	}
	return (q[j].y-q[i].y)/(q[j].x-q[i].x);
}
bool cmp(asd aa,asd bb){
	return aa.k>bb.k;
}
int que[maxn];
void solve(rg int l,rg int r){
	if(l==r){
		f[q[l].id]=std::max(f[q[l].id],f[q[l].id-1]);
		q[l].y=f[l]/(q[l].a*q[l].rat+q[l].b);
		q[l].x=q[l].y*q[l].rat;
		return;
	}
	rg int mids=(l+r)>>1,head=l-1,tail=mids;
	for(rg int i=l;i<=r;i++){
		if(q[i].id<=mids) tmp[++head]=q[i];
		else tmp[++tail]=q[i];
	}
	for(rg int i=l;i<=r;i++) q[i]=tmp[i];
	solve(l,mids);
	head=1,tail=0;
	for(rg int i=l;i<=mids;i++){
		while(tail>1 && xl(que[tail],i)>=xl(que[tail-1],que[tail])) tail--;
		que[++tail]=i;
	}
	for(rg int i=mids+1;i<=r;i++){
		while(head<tail && xl(que[head],que[head+1])>=q[i].k) head++;
		f[q[i].id]=std::max(f[q[i].id],q[que[head]].y*q[i].b+q[que[head]].x*q[i].a);
	}
	solve(mids+1,r);
	head=l,tail=mids+1;
	for(rg int i=l;i<=r;i++){
		if(tail>r || (head<=mids && q[head].x<=q[tail].x)) tmp[i]=q[head++];
		else tmp[i]=q[tail++];
	}
	for(rg int i=l;i<=r;i++) q[i]=tmp[i];
}
int main(){
	scanf("%d%d",&n,&s);
	for(rg int i=1;i<=n;i++){
		scanf("%lf%lf%lf",&q[i].a,&q[i].b,&q[i].rat);
		q[i].id=i,q[i].k=-q[i].a/q[i].b;
	}
	f[0]=s;
	std::sort(q+1,q+1+n,cmp);
	solve(1,n);
	printf("%.3f\n",f[n]);
	return 0;
}

八、P2305 [NOI2014] 购票

题目传送门

分析

上一道题的强化版

把序列上的问题搬到了树上

解决方法也由 \(CDQ\) 分治变成了点分治

\(DP\) 方程显然:\(f_i=min(f_j+(dis_i-dis_j) \times p_i+q_i)\)

\(dis\) 是根到每个点的距离

这个式子可以斜率优化

但是暴力建凸包肯定不行

考虑用点分治,顺序是

\(1\)、处理当前子树

\(2\)、找当前子树的重心

\(3\)、一起分治重心和根节点

\(4\)、得到根节点到子树重心之间链的答案

\(5\)、用这个答案来更新重心其他子树的 \(ans\)

\(6\)、继续分治重心的其他子树

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#define rg register
const int maxn=2e5+5;
int h[maxn],tot=1,n,t,fa[maxn],p[maxn],maxsiz[maxn];
struct asd{
	int to,nxt;
}b[maxn<<1];
void ad(rg int aa,rg int bb){
	b[tot].to=bb;
	b[tot].nxt=h[aa];
	h[aa]=tot++;
}
long long dis[maxn],f[maxn],s[maxn],lv[maxn],q[maxn];
void dfs(rg int now){
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(u==fa[now]) continue;
		dis[u]=dis[now]+s[u];
		dfs(u);
	}
}
int rt,totsiz,siz[maxn];
bool vis[maxn];
void getroot(rg int now){
	maxsiz[now]=0,siz[now]=1;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(vis[u] || u==fa[now]) continue;
		getroot(u);
		siz[now]+=siz[u];
		maxsiz[now]=std::max(maxsiz[now],siz[u]);
	}
	maxsiz[now]=std::max(maxsiz[now],totsiz-siz[now]);
	if(maxsiz[now]<maxsiz[rt]) rt=now;
}
double xl(rg int i,rg int j){
	return ((double)f[i]-(double)f[j])/((double)dis[i]-(double)dis[j]);
}
bool cmp(rg int aa,rg int bb){
	return dis[aa]-lv[aa]>dis[bb]-lv[bb];
}
int sta[maxn],tp,sta2[maxn],tp2;
int ef(rg double now){
	rg int l=1,r=tp,mids;
	while(l<r){
		mids=(l+r)>>1;
		if(xl(sta[mids],sta[mids+1])<=now) r=mids;
		else l=mids+1;
	}
	return sta[l];
}
void dfs2(rg int now,rg int lat){
	sta2[++tp2]=now;
	for(rg int i=h[now];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(!vis[u] && u!=lat) dfs2(u,now);
	}
}
void solve(rg int now){
	rt=0;
	getroot(now);
	vis[rt]=1;
	rg int nrt=rt;
	if(now!=nrt){
		totsiz=siz[now]-siz[nrt];
		solve(now);
		rg int jl=nrt,cs=0;
		tp=tp2=0;
		dfs2(nrt,0);
		std::sort(sta2+1,sta2+1+tp2,cmp);
		for(rg int i=1;i<=tp2;i++){
			while(jl!=fa[now] && dis[sta2[i]]-dis[jl]<=lv[sta2[i]]){
				while(tp>1 && xl(jl,sta[tp])>=xl(sta[tp],sta[tp-1])) tp--;
				sta[++tp]=jl;
				jl=fa[jl];
			}
			if(tp){
				cs=ef((double)p[sta2[i]]);
				f[sta2[i]]=std::min(f[sta2[i]],f[cs]+1LL*dis[sta2[i]]*p[sta2[i]]-1LL*dis[cs]*p[sta2[i]]+1LL*q[sta2[i]]);
			}
		}
	} else {
		tp=tp2=0;
		dfs2(nrt,0);
	}
	for(rg int i=1;i<=tp2;i++){
		if(dis[sta2[i]]-dis[nrt]<=lv[sta2[i]]){
			f[sta2[i]]=std::min(f[sta2[i]],f[nrt]+1LL*dis[sta2[i]]*p[sta2[i]]-1LL*dis[nrt]*p[sta2[i]]+1LL*q[sta2[i]]);
		}
	}
	for(rg int i=h[nrt];i!=-1;i=b[i].nxt){
		rg int u=b[i].to;
		if(!vis[u]){
			totsiz=siz[u];
			solve(u);
		}
	}
}
int main(){
	memset(h,-1,sizeof(h));
	memset(f,0x3f,sizeof(f));
	scanf("%d%d",&n,&t);
	for(rg int i=2;i<=n;i++){
		scanf("%d%lld%d%lld%lld",&fa[i],&s[i],&p[i],&q[i],&lv[i]);
		ad(i,fa[i]);
		ad(fa[i],i);
	}
	f[1]=0;
	dfs(1);
	totsiz=n,maxsiz[0]=0x3f3f3f3f;
	solve(1);
	for(rg int i=2;i<=n;i++) printf("%lld\n",f[i]);
	return 0;
}
posted @ 2021-01-04 15:42  liuchanglc  阅读(352)  评论(0编辑  收藏  举报