[CTSC2016]时空旅行 (线段树分治)

前言

昨天学习了线段树分治,算法比较抽象,没有学得太具体,今天做一道例题练练手
——自闭前

题面上洛谷

题意

维护若干个集合,每个集合都是由一个编号比它小的集合加入一个二元组 ( x , c ) (x,c) (x,c)或者删去一个元素拓展而来的。如此,集合的拓展关系之间构成一个树形结构。给出 m m m次询问,给出一个 k k k X X X,询问第 k k k个集合中 ( x − X ) 2 + c (x-X)^2+c (xX)2+c的最小值。

n , m < = 5 e 5 n,m<=5e5 n,m<=5e5

思路

首先,如果 X X X值是固定的,可以用主席树维护集合进行区间最小值的处理。
然而 X X X值并不是固定的,这个时候就没有头绪了,我们试着从式子入手对这个问题进行求解。

下面来推一波公式:


设区间的两个二元组 ( x 1 , c 1 ) 、 ( x 2 , c 2 ) (x1,c1)、(x2,c2) (x1,c1)(x2,c2)。若 x 1 x1 x1优于 x 2 x2 x2,设

y 1 = ( x 1 − X ) 2 + c 1 , y 2 = ( x 2 − X ) 2 + c 2 y1=(x1-X)^2+c1,y2=(x2-X)^2+c2 y1=(x1X)2+c1y2=(x2X)2+c2,则 y 1 − y 2 < 0 y1-y2<0 y1y2<0
简单的平方差公式可以得到:
( x 1 + x 2 − 2 X ) ( x 1 − x 2 ) + c 1 − c 2 < 0 (x1+x2-2X)(x1-x2)+c1-c2<0 (x1+x22X)(x1x2)+c1c2<0
( x 1 + x 2 − 2 X ) < c 2 − c 1 x 1 − x 2 (x1+x2-2X)<\frac{c2-c1}{x1-x2} (x1+x22X)<x1x2c2c1
发现上一步的小于号由于 x 1 − x 2 x1-x2 x1x2的正负号可能不成立。我们即使用一个小技巧:我们按照 x x x从大到小排序插入。进一步考虑:
2 X > c 1 − c 2 x 1 − x 2 + x 1 + x 2 2X>\frac{c1-c2}{x1-x2}+x1+x2 2X>x1x2c1c2+x1+x2
k = c 1 − c 2 x 1 − x 2 , b = x 1 + x 2 k=\frac{c1-c2}{x1-x2},b=x1+x2 k=x1x2c1c2,b=x1+x2
如此,我们可以维护左边式子的一个上凸包,从而进行集合中二分地求解。

但是这样其实并不方便维护,我们写作另一种形式:

x i 2 + c i − x j 2 − c j x i − x j < 2 X \frac{xi^2+ci-xj^2-cj}{xi-xj}<2X xixjxi2+cixj2cj<2X

这种形式维护就更方便了。


下面我们将问题转换:如何维护集合中的这个凸包。我们发现:一个集合可以用一个叶节点到根的信息进行维护。那么我们会想到:

dfs序+线段树(维护时间区间)!(其实我并没有想到

如果是插入一个元素,会影响一个区间的整棵子树,对于标记的下放是极其麻烦的,所以应该直接在线段树上插入元素。

如果是删除一个元素,对于这个凸包是不能够维护的,若是重构将绝杀,可惜会TLE,但是不用怕,这道题秒不了我。我们可以将删除操作转化:考虑进行线段树上可持久化,或者是类似节点的回退:推到这里,我们就自然而然地想到了线段树分治:从而进行线段树的节点回退。假如我在 u u u这个节点加了一个 x x x元素,那么应该在 [ L [ u ] , R [ u ] ] [L[u],R[u]] [L[u],R[u]]这个区间修改,如果我在 v v v这个节点把 x x x删了( v v v一定是 u u u的后代,而且除了 u u u的节点的加操作不可能加的是 x x x星球,这个是题意保证),那么我应该在 [ L [ v ] , R [ v ] ] [L[v],R[v]] [L[v],R[v]]做删除操作,可是我们做不了删除操作,于是拆成俩区间 [ L [ u ] , L [ v ] − 1 ] [L[u],L[v]−1] [L[u],L[v]1] [ R [ v ] + 1 , R [ u ] ] [R[v]+1,R[u]] [R[v]+1,R[u]]不就好了?总的区间个数还是 O ( n ) O(n) O(n)级别的,当然具体拆的时候还要一些特判。

对于维护凸包,每个节点保存一个单调栈,当一个区间被修改操作完全包含的时候,就在这个单调栈弹出。而询问可以离线的。首先,一个集合里面的答案可以二分。

那线段树上怎么做呢。还是先把询问排序,来我们想一下每个节点有个 p o s pos pos表示上一次最小值取到的位置(初始是0),如果有询问问到这里,我就尝试往后挪,能挪又不会越界的时候就挪就好了。复杂度是 O ( n l o g n ) O(nlogn) O(nlogn)的,然后挨着挨着问就好了。

记得将 0 0 0号集合挂入线段树。

Code:
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define DB double
#define MAXN 500005
#define LL long long
#define Int register int
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
inline void read(LL &x)
{
	x = 0;
	LL f = 1;
	char s = getchar();
	while (s < '0' || s > '9')
	{
		if (s == '-')
			f = -1;
		s = getchar();
	}
	while (s >= '0' && s <= '9')
	{
		x = (x << 3) + (x << 1) + (s ^ 48);
		s = getchar();
	}
	x *= f;
}
inline void Read(int &x)
{
	x = 0;
	int f = 1;
	char s = getchar();
	while (s < '0' || s > '9')
	{
		if (s == '-')
			f = -1;
		s = getchar();
	}
	while (s >= '0' && s <= '9')
	{
		x = (x << 3) + (x << 1) + (s ^ 48);
		s = getchar();
	}
	x *= f;
}
inline LL Max(LL x,LL y)
{
	return x > y ? x : y;
}
inline LL Min(LL x,LL y)
{
	return x < y ? x : y;
}
vector<int> G[MAXN];
vector<int> TL[MAXN], TR[MAXN];
LL Ys[MAXN], Valx[MAXN], c[MAXN];
int Cnt, Dfn[MAXN], Index[MAXN];
bool cmp(int A,int B)
{
	return Valx[A] < Valx[B]; 
}
void Dfs(int x,int Fa)
{
	Dfn[x] = ++ Cnt;
	if (Ys[x] > 0) // 添加元素拓展而来,为时间区间的左端点 
		TL[Ys[x]].push_back( Cnt );
	if (Ys[x] < 0) // 删除元素而来,为时间区间前一个点为时间区间的右端点 
		TR[- Ys[x]].push_back(Cnt - 1);
	int l = G[x].size();
	for (Int i = 0; i < l; ++ i)
	{
		LL to = G[x][i];
		if (to != Fa)
			Dfs(to, x);
	}
	if (Ys[x] > 0)
		TR[Ys[x]].push_back( Cnt );
	if (Ys[x] < 0)
		TL[- Ys[x]].push_back(Cnt + 1);
}
int L[MAXN << 2], R[MAXN << 2];
void Build(int x,int l,int r)
{
	L[x] = 0, R[x] = -1;
	if (l == r)
		return ;
	LL Mid = (l + r) / 2;
	Build(x << 1, l, Mid);
	Build(x << 1 | 1, Mid + 1, r);
}
vector<int> Tb[MAXN << 2];
inline DB K(int A,int B)
{
	return ((DB)(Valx[A] * Valx[A] + c[A] - Valx[B] * Valx[B] - c[B])) / (Valx[A] - Valx[B]);
}
void GetTub(int x,int l,int r,int Tl,int Tr,int Now)
// 线段树节点信息,时间区间,集合元素 
{
	if (l == Tl && r == Tr) // 树节点区间就为修改/询问区间,对节点上的凸包进行维护 
	{
		while (Tb[x].size() <= R[x] + 5)
			 Tb[x].push_back( 0 ); // 防止vector空间不够
		if (L[x] <= R[x] && Valx[Tb[x][R[x]]] == Valx[Now])
		{
			if (c[Tb[x][R[x]]] <= c[Now])
				return ;
			R[x] --; // 去重去杂 
		}
		while (L[x] < R[x] && K(Now, Tb[x][R[x]]) < K(Tb[x][R[x]], Tb[x][R[x] - 1]))
			R[x] --; // 维护上凸包
		Tb[x][++ R[x]] = Now;
		return ; 
	}
	LL Mid = (l + r) / 2;
	if (Tr <= Mid)
		GetTub(x << 1, l, Mid, Tl, Tr, Now);
	else if (Tl > Mid)
		GetTub(x << 1 | 1, Mid + 1, r, Tl, Tr, Now);
	else GetTub(x << 1, l, Mid, Tl, Mid, Now), GetTub(x << 1 | 1, Mid + 1, r, Mid + 1, Tr, Now);
}
struct node
{
	LL X; int Xu, Id;
	node(){}
	node(int XU,LL XX,int ID)
	{
		Xu = XU;
		X = XX;
		Id = ID;
	}
	friend bool operator <(node A,node B)
	{
		return A.X < B.X;
	}
}Ask[MAXN];
LL Get;
void Query(LL x,LL l,LL r,LL Pos,LL X)
{
	while (L[x] < R[x] && K(Tb[x][L[x] + 1], Tb[x][L[x]]) < 2.0 * X)
		L[x] ++;
	if (L[x] <= R[x] && Tb[x].size() > 0)
		Get = Min(Get, (Valx[Tb[x][L[x]]] - X) * (Valx[Tb[x][L[x]]] - X) + c[Tb[x][L[x]]]);
	if (l == r)
		return ;
	LL Mid = (l + r) / 2;
	if (Pos <= Mid)
		Query(x << 1, l, Mid, Pos, X);
	else Query(x << 1 | 1, Mid + 1, r, Pos, X);
}
LL Ans[MAXN];
int main()
{
	int n, m;
	Read( n ); Read( m ); read( c[0] );
	for (Int i = 1; i < n; ++ i)
	{
		int Or;
		Read( Or );
		if (! Or)
		{
			int fr, id;
			Read( fr ); Read( id );
			int y, z;
			read( Valx[id] ); Read( y ); Read( z ); read( c[id] );
			Ys[i] = id;
			G[fr].push_back( i );
			G[i].push_back( fr ); 
		}
		else
		{
			int fr, id;
			Read( fr ); Read( id );
			Ys[i] = - id;
			G[fr].push_back( i );
			G[i].push_back( fr );  
		}
	}
	Dfs(0, 0); // 求出Dfs序从而得到树形区间
	Build(1, 1, n);
	for (Int i = 1; i <= n; ++ i)
		Index[i] = i;
	//for (Int i = 1; i <= n; ++ i)
		//printf("%lld\n", Valx[i]);
	sort(Index + 1, Index + 1 + n, cmp); // 得到x递增序列
	//for (Int i = 1; i <= n; ++ i)
		//printf("%lld\n", Index[i]);	  
	GetTub(1, 1, n, 1, n, 0); // 0号挂上去
	for (Int i = 1; i <= n; ++ i) // 得到凸包
	{
		LL Cha = Index[i];
		//printf("%lld\n", Cha);
		int Len = TL[Cha].size();
		for (Int p = 0; p < Len; ++ p)
		{
			LL Chal = TL[Cha][p], Char = TR[Cha][p];
			if (Chal <= Char)
			{
				//printf("You %lld %lld %lld\n", Chal, Char, Cha);
				GetTub(1, 1, n, Chal, Char, Cha);
			}
		}
	}
	for (Int i = 1; i <= m; ++ i)
	{
		Read( Ask[i].Xu ); read( Ask[i].X );
		Ask[i].Id = i;
	}
	sort(Ask + 1, Ask + 1 + m);
	for (Int i = 1; i <= m; ++ i)
	{
		Get = INF;
		Query(1, 1, n, Dfn[Ask[i].Xu], Ask[i].X);
		Ans[Ask[i].Id] = Get;
	}
	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、付费专栏及课程。

余额充值