前言
昨天学习了线段树分治,算法比较抽象,没有学得太具体,今天做一道例题练练手
——自闭前
题意
维护若干个集合,每个集合都是由一个编号比它小的集合加入一个二元组 ( 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 (x−X)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=(x1−X)2+c1,y2=(x2−X)2+c2,则
y
1
−
y
2
<
0
y1-y2<0
y1−y2<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+x2−2X)(x1−x2)+c1−c2<0
(
x
1
+
x
2
−
2
X
)
<
c
2
−
c
1
x
1
−
x
2
(x1+x2-2X)<\frac{c2-c1}{x1-x2}
(x1+x2−2X)<x1−x2c2−c1
发现上一步的小于号由于
x
1
−
x
2
x1-x2
x1−x2的正负号可能不成立。我们即使用一个小技巧:我们按照
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>x1−x2c1−c2+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=x1−x2c1−c2,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 xi−xjxi2+ci−xj2−cj<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;
}
注意卡常。