[CTSC2016]时空旅行(斜率优化+线段树分治)

洛谷题目传送门

解题思路

首先发现只有 x x x c c c是有用的
这些时空构成了一棵树,我们实际上要找一个点 i i i,满足对于给出的 X X X
m i n ( ( X − x i ) 2 + c i ) min((X-x_i)^2+c_i) min((Xxi)2+ci)
根据斜率优化的套路
我们展开式子
a n s = ( X − x i ) 2 + c i = X 2 − 2 x i X + x i 2 + c i ans=(X-x_i)^2+c_i=X^2-2x_iX+x_i^2+c_i ans=(Xxi)2+ci=X22xiX+xi2+ci
x i 2 + c i = a n s + 2 ∗ x i ∗ X − X 2 x_i^2+c_i=ans+2*x_i*X-X^2 xi2+ci=ans+2xiXX2
那么可以把每个星球看成一个点 ( 2 × x i , x i 2 + c i ) (2\times x_i,x_i^2+c_i) (2×xi,xi2+ci)然后用一条斜率为 X X X的凸包截这些点,所能获得的最大截距
我们只需要维护一个上凸壳就行了
然后我们就得到了一个思路
我们建立一颗以树的 d f s dfs dfs为序列的线段树,这样每个星球存在的范围是若干区间,并对于每个区间都维护一个上凸壳,然后为了更方便的建立凸包,我们先把 x x x坐标排序,然后再把每个星球插入到它在线段树上存在的区间里
查询的时候如果在线的话每次可能吧整个凸包遍历一遍
所以我们把这些询问按照询问的斜率排序,然后对于每个节点记录一个 c u r [ x ] cur[x] cur[x]表示当前扫描到了这个凸包的那个点,因为排过序了所以不可能访问之前的点,也就是说最多访问整个树的凸包一遍,而每个点最多在 O ( l o g n ) O(logn) O(logn)个区间里,所以我们的复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
对于怎么处理每个星球在线段树上的区间,可以这样考虑
首先对于 d f s dfs dfs序来说,第一个有这个星球的节点就是这个星球出现的位置
所以星球只可能在这个节点的子树内出现了
然后之后每次出现的时候,就代表删除,所以这个点的子树内的点都没有这个星球,记录一下上一次出现的位置,那么当前区间就是 [ e d l a s t + 1 , d f n i ] [ed_{last}+1,dfn_i] [edlast+1,dfni]

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5+7;
typedef double db;
const LL INF = 1e18+7;
inline LL read()
{
	int X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
int n,m;
LL C[N],X[N];
int note[N];
struct edge
{
	int y,next;
}e[2*N];
struct point
{
	LL x,y;
}p[N];
struct Query
{
	int X,s,id;
}Q[N];
point Point(LL x,LL y)
{
	return (point){x,y};
}
int link[N],t=0;
void add(int x,int y)
{
	e[++t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
int dfn[N],End[N],tot=0;
vector<int> G[N];
int id[N];
void dfs(int x)
{
	dfn[x]=++tot;
	G[note[x]].push_back(x);
	for(int i=link[x];i;i=e[i].next)
	{
		int y=e[i].y;
		dfs(y);
	}
	End[x]=tot;
}
bool cmp(int i,int j)
{
	if(X[i]==X[j]) return C[i]>C[j];
	return X[i]<X[j];
}
bool cmp2(Query a,Query b)
{
	return a.X<b.X;
}
vector<int> A[N*4];
db slope(int i,int j)
{
	return (db)(p[j].y-p[i].y)/(p[j].x-p[i].x);
}
void Add(int k,int i)
{
	int sz=A[k].size();
	while(sz>1&&(p[A[k][sz-1]].y-p[A[k][sz-2]].y)*(p[i].x-p[A[k][sz-1]].x)>=(p[i].y-p[A[k][sz-1]].y)*(p[A[k][sz-1]].x-p[A[k][sz-2]].x))
	{
		sz--;
		A[k].pop_back();
	}
	A[k].push_back(i);
	return;
}
void Insert(int k,int l,int r,int L,int R,int i)
{
	if(L<=l&&r<=R)
	{
		Add(k,i);
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid) Insert(k<<1,l,mid,L,R,i);
	if(R>mid) Insert(k<<1|1,mid+1,r,L,R,i);
}
int cur[N*4];
LL Ask(int k,int l,int r,int x,int K)
{
	LL res=INF;
	int sz=A[k].size();
	if(sz)
	{
		while(cur[k]<sz-1&&p[A[k][cur[k]+1]].y-p[A[k][cur[k]]].y<1ll*K*(p[A[k][cur[k]+1]].x-p[A[k][cur[k]]].x)) 
		{
			++cur[k];
		}
		res=p[A[k][cur[k]]].y-p[A[k][cur[k]]].x*K+1ll*K*K;
	}
	if(l==r) return res;
	int mid=(l+r)>>1;
	if (x<=mid) return min(res,Ask(k<<1,l,mid,x,K));
	else return min(res,Ask(k<<1|1,mid+1,r,x,K));
}
void Build()
{
	for(int i=1;i<=n;i++)
	{
		int x=id[i];
		if(!G[x].empty())
		{
			p[i]=Point(2ll*X[x],1ll*X[x]*X[x]+C[x]);
			int lst=dfn[G[x][0]];
			int siz=G[x].size();
			for(int j=1;j<siz;j++)
			{
				int y=G[x][j];
				Insert(1,1,n,lst,dfn[y]-1,i);
				lst=End[y]+1;
			}
			if(lst<=End[G[x][0]]) Insert(1,1,n,lst,End[G[x][0]],i);
		}
	}
}
LL Ans[N];
int main()
{
	n=read();
	m=read();
	C[1]=read();
	note[1]=1;
	for(int i=2;i<=n;i++)
	{
		int opt=read(),ex;
		if(opt==0)
		{
			int Fa=read()+1,id=read()+1;
			X[id]=read();
			ex=read();ex=read();
			C[id]=read();
			add(Fa,i);
			note[i]=id;
		}
		else
		{
			int Fa=read()+1,id=read()+1;
			add(Fa,i);
			note[i]=id;
		}
	}
	dfs(1);
	for(int i=1;i<=n;i++) id[i]=i;
	sort(id+1,id+n+1,cmp);
	Build();
	for(int i=1;i<=m;i++)
	{
		int s=read()+1,x=read();
		Q[i]=(Query){x,dfn[s],i};
	}
	sort(Q+1,Q+m+1,cmp2);
	for(int i=1;i<=m;i++)
	Ans[Q[i].id]=Ask(1,1,n,Q[i].s,Q[i].X);
	for(int i=1;i<=m;i++)
	printf("%lld\n",Ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值