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

题目
简化题意:你需要维护\(n\)个集合,集合元素为二元组\((x,v)\)。集合\(i\)的产生方式是以某个原有集合\(p_i\)为样本,扩展或删除一个元素后得到新集合。有\(q\)次询问,每次给出\(y\)并指定一个集合\(i\),要求从集合\(i\)中找出一个元素,最小化\((x?y)^2+v\)。
先拆式子\((x-y)^2+v=x^2-2xy+y^2+v\),令其等于\(k\)即\(x^2+y^2-2xy+v=k\)。
移项得\(2yx+k=y^2+x^2+v\),可以看作是\((x,x^2+y^2+v)\)这一决策点,\(2y\)为斜率,\(k\)就为\(y\)轴截距。所以这个可以维护凸壳来解决。
我们知道每一个元素在\(dfs\)序中会以一段一段区间的形式出现。
每个点不管加入还是删除元素都会造成一个新的区间,所以区间总数是\(O(n)\)的。
将元素按照\(x\)升序排序后插入线段树,每个节点维护一个凸壳。
询问按\(y\)升序排序,可以使得每次操作可是直接取上一次凸壳最前面的值。

#include<bits/stdc++.h>
#define LL long long
#define db double
#define pb push_back
#define ls p<<1
#define rs p<<1|1
#define mid ((l+r)>>1)
#define INF 1e18
using namespace std;
const int N=500007;
int L[N<<2],R[N<<2],t[N],dfn[N],T[N],n,m,Time;
LL C[N],ans[N],val[N];
vector<int>G[N],al[N],ar[N],w[N<<2];
struct node{LL x,val,id;}a[N];
int cmp(int a,int b){return val[a]<val[b];}
int operator<(node a,node b){return a.val<b.val;}
LL min(LL a,LL b){return a<b? a:b;}
db K(int x ,int y){return (val[x]*val[x]+C[x]-val[y]*val[y]-C[y])/(db)(val[x]-val[y]);}
void dfs(int u,int fa)
{
    dfn[u]=++Time;
    if(T[u]>0) al[T[u]].pb(Time);
    if(T[u]<0) ar[-T[u]].pb(Time-1);
    for(int i=G[u].size()-1,v;~i;--i) if((v=G[u][i])^fa) dfs(v,u);
    if(T[u]>0) ar[T[u]].pb(Time);
    if(T[u]<0) al[-T[u]].pb(Time+1);
}
void build(int p,int l,int r)
{
    L[p]=0,R[p]=-1;
    if(l==r) return ;
    build(ls,l,mid),build(rs,mid+1,r);
}
void update(int p,int l,int r,int ql,int qr,int P)
{
    if(ql==l&&r==qr)
    {
    while(w[p].size()<=R[p]+5) w[p].pb(0);
    if(L[p]<=R[p]&&val[w[p][R[p]]]==val[P])
    {
        if(C[w[p][R[p]]]<=C[P]) return ;
        --R[p];
    }
    while(L[p]<R[p]&&K(w[p][R[p]],P)<K(w[p][R[p]],w[p][R[p]-1])) --R[p];
    w[p][++R[p]]=P;
    return ;
    }
    if(qr<=mid) update(ls,l,mid,ql,qr,P);
    else if(ql>mid) update(rs,mid+1,r,ql,qr,P);
    else update(ls,l,mid,ql,mid,P),update(rs,mid+1,r,mid+1,qr,P);
}
LL query(int p,int l,int r,int x,LL t,LL sum)
{
    LL ans=INF;
    while(L[p]<R[p]&&K(w[p][L[p]],w[p][L[p]+1])<=2.0*t) ++L[p];
    if(L[p]<=R[p]&&w[p].size()>0) ans=(t-val[w[p][L[p]]])*(t-val[w[p][L[p]]])+C[w[p][L[p]]];
    ans=min(ans,sum);
    if(l==r) return ans;
    return x<=mid? query(ls,l,mid,x,t,ans):query(rs,mid+1,r,x,t,ans);
}
int main()
{
    int i,j,k,u,x,opt,l,r;
    scanf("%d%d%lld",&n,&m,&C[0]);
    for(i=1;i<n;++i) scanf("%d%d%d",&opt,&u,&x),G[u].pb(i),(opt? (T[i]=-x):(T[i]=x,scanf("%lld%d%d%lld",&val[x],&u,&u,&C[x])));
    dfs(0,0),build(1,1,n);
    for(i=1;i<=n;++i) t[i]=i;
    sort(t+1,t+n+1,cmp),update(1,1,n,1,n,0);
    for(i=1;i<=n;++i) for(k=t[i],j=0;j<al[k].size();++j) if((l=al[k][j])<=(r=ar[k][j])) update(1,1,n,l,r,k);
    for(i=1;i<=m;++i) scanf("%lld%lld",&a[i].x,&a[i].val),a[i].id=i;
    sort(a+1,a+m+1);
    for(i=1;i<=m;++i) ans[a[i].id]=query(1,1,n,dfn[a[i].x],a[i].val,INF);
    for(i=1;i<=m;++i) printf("%lld\n",ans[i]);
}

原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/11519553.html

时间: 2024-10-07 16:22:08

Luogu P5416 [CTSC2016]时空旅行(线段树分治)的相关文章

luogu P5416 [CTSC2016]时空旅行

luogu uoj 注意到用这个集合产生方式可以构建出一个树型结构,并且每个加入/删除元素都是对应的一个子树的范围,对应到dfs序上就是每次对一个区间内的集合加入/删除元素,所以可以线段树分治,把每种元素的出现区间整出来 把答案柿子\((x-x_0)^2+c\)拆开,得到\(x^2-2x*x_0+{x_0}^2+c\),然后每个元素就相当于斜率为\(-2x\),截距为\(x^2+c\)的直线,所以线段树每个节点维护凸壳,要用的时候直接查对应横坐标的值.如果直接做是两个\(log\)的,但是因为要

[CTSC2016]时空旅行(线段树+凸包)

应该是比较套路的,但是要A掉仍然不容易. 下面理一下思路,思路清楚了也就不难写出来了. 0.显然y,z坐标是搞笑的,忽略即可. 1.如果x不变,那么直接set即可解决. 2.考虑一个空间和询问x0,通过化式子发现实际上就是:把每个星球看成一个一次函数,其实是在询问这个空间内的所有一次函数在x0处的最小值. 3.这个显然是一个凸包,所以我们需要对每个空间维护一个凸包,由空间整体呈树状,可以想到用DFS序+线段树维护区间. 4.预处理出每个星球的存在范围,在线段树上永久化标记.查询时依次递归求最小值

线段树分治总结

目录 类型一 例题1:八纵八横 代码: 例题2:时空旅行 首先,要求可以离线. 线段树分治有两种. 类型一 操作基于区间,单点询问. 有时,进行的一种操作可以快速完成,但是,要实现这种操作的逆操作较难. 因为,通常情况下,需要实现的逆操作都是很久以前执行的. 但是,如果只撤销上次操作,就会简单得多. 比如,维护一些连通性,或直径,线性基等问题. 这类问题加边很好做,但删边很难实现. 我们可以扫一遍操作,得到每个操作的有效区间. 然后,将每个添加操作的有效区间按在线段树上,然后遍历这颗线段树同时处

【原创】洛谷 LUOGU P3373 【模板】线段树2

P3373 [模板]线段树 2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格式: 第一行包含三个整数N.M.P,分别表示该数列数字的个数.操作的总个数和模数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x y k 含义:将区间[x,y]内每个数乘上k 操作2: 格式:2 x

3237: [Ahoi2013]连通图 线段树分治

题解: 线段树分治裸题 apio t1是这个所以就学习了一下 #include <bits/stdc++.h> using namespace std; const int N=2e5+10; struct re{ int x,y; }a[N*2]; int b[N+20][5],cnt,now,n,m,k; int ls[N*15],rs[N*15],data[N*15],last[N+20]; int ph[N*4],pt[N*4],count2[N+20],f[N]; bool ft[N

[CTSC2016]时空旅行

description 题面 solution 线段树分治+斜率优化毒瘤题 题目可以简化为: 你要维护一个包含元素\((x,c)\)的集合 修改操作为从以前的一个版本更新,修改内容为添加或删除一个元素 查询操作给出\(x_0\),查询某个版本中的\(max\{(x-x_0)^2+c\}\) 可以知道版本之间的时间关系形成一颗树 如果在一个版本删除了某个元素,那么在这个版本的子树中都不会再有这个版本 由于子树的\(dfn\)是连续的,因此操作可以简化为在序列上进行,总共有\(O(m)\)个区间 最

线段树分治总结(线段树分治,线段树,并查集,树的dfn序,二分图染色)

闲话 stO猫锟学长,满脑子神仙DS 线段树分治思想 我们在做CDQ的时候,将询问和操作通通视为元素,在归并过程中统计左边的操作对右边的询问的贡献. 而在线段树分治中,询问被固定了.按时间轴确定好询问的序列以后,我们还需要所有的操作都会影响一个时间区间.而这个区间,毫无疑问正好对应着询问的一段区间. 于是,我们可以将每一个操作丢到若干询问里做区间修改了,而线段树可以高效地维护.我们开一个叶子节点下标为询问排列的线段树,作为分治过程的底层结构. 具体的实现,仍然要看题目. 例题1 BZOJ4025

算法学习——动态图连通性(线段树分治+按秩合并并查集)

在考场上遇到了这个的板子题,,,所以来学习了一下线段树分治 + 带撤销的并查集. 题目大意是这样的:有m个时刻,每个时刻有一个加边or撤销一条边的操作,保证操作合法,没有重边自环,每次操作后输出当前图下所有联通块大小的乘积. 首先观察到如果没有撤销操作,那么直接用并查集就可以维护,每次合并的时候乘上要合并的两个并查集大小的逆元,然后乘上合并之后的大小即可. 那么来考虑撤销,观察到如果并查集不带路径压缩,应该是可以处理撤销操作的. 但我们并不能直接做,因为并查集的撤销必须按顺序来,就相当于每次合并

【线段树分治 线性基】luoguP3733 [HAOI2017]八纵八横

不知道为什么bzoj没有HAOI2017 题目描述 Anihc国有n个城市,这n个城市从1~n编号,1号城市为首都.城市间初始时有m条高速公路,每条高速公路都有一个非负整数的经济影响因子,每条高速公路的两端都是城市(可能两端是同一个城市),保证任意两个城市都可以通过高速公路互达. 国正在筹划“八纵八横”的高铁建设计划,计划要修建一些高速铁路,每条高速铁路两端也都是城市(可能两端是同一个城市),也都有一个非负整数的经济影响因子.国家还计划在“八纵八横”计划建成之后,将“一带一路”扩展为“一带_路一