题意:数轴上有无穷个格子,每个坐标上各有一个格子,你需要支持以下操作共 \(n\) 次:
- 在 \(x=k\) 这个格子前插入一个格子,并将所有 \(x\geq k\) 的格子后移一位。时间++。
- 询问当前 \(x=k\) 这个格子在时刻 \(tim\) 的坐标。
\(n\leq 5\times 10^5,x\leq 10^9\)。
推了一会好像不能用可持久化的东西维护,可能是我姿势有问题。
普通的树套树是外层平衡树维护坐标维度,内层动态开点线段树维护时间维度。修改转变为第一维区间、第二维单点,询问转变为第一维单点、第二维单点。时间复杂度 \(O(n\log ^2n)\),而且外层貌似只能用替罪羊树,因为不能转(父子关系不能变)。
介绍一个常数更小的 \(O(n\log ^2n)\) 的做法:
考虑外层用线段树维护时间维度,然后线段树上每个节点 \(u\) 存储一个有序数组 \(a_u\):表示 \(u\) 代表时间区间 \([l,r]\) 内进行的操作一所插入的所有格子在时刻 \(r\) 时的坐标。
维护这个数组的目的是:假设我们知道某个格子在时刻 \(r\) 时的坐标 \(x\),我们想将时间倒流,得到它在时刻 \(l\) 时的坐标 \(x'\)(假设它那时也存在),那么我们只需要用 \(x\) 减去 \(a_u\) 中小于等于 \(x\) 的数的个数即可得到 \(x'\),即减掉时间区间 \([l,r]\) 内操作一的影响。
那么询问的时候假设我们已经找到了 \(x=k\) 这个格子是 \(st\) 时刻被插入的(如果没有置为 \(0\)),假设当前时刻为 \(now\),然后我们直接在线段树上逆序处理时间区间 \([st,now]\) 内的所有操作一的影响即可。而找到 \(x=k\) 这个格子是在什么时候被插入的的方法类似。询问的时间复杂度为 \(O(n\log ^2n)\)。
那么我们现在考虑如何维护这个数组了。首先就是 merge 的问题,普通的做法是拿 \(a_{lc}\) 到 \(a_{rc}\) 上二分找出位置。但由于现在两个数组都是有序的,所以可以直接使用双指针做到 \(O(|a_{lc}|+|a_{rc}|)\)。
显然我们直接每次修改都从叶子往上 merge 的时间复杂度是有问题的。但有一个神奇的技巧:每次我们在时刻 \(k\) 修改某个叶子时,我们只更新那些 \(r=k\) 的区间。原因很简单:首先 \(r<k\) 的区间我们不会影响到,其次对于那些 \(r>k\) 的区间,在下一次操作一之前的询问我们都不会问到它,所以这些区间是暂时不需要更新的。换言之,只有当本区间内的所有操作都发生之后,我们才更新这个区间。这样能保证每个区间只被更新一次,修改的总时间复杂度也就是 \(O(n\log n)\) 的了。
本题是放到二维的情况,不过也是类似的。
代码如下:
#include<bits/stdc++.h>
#define N 100010
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
int Q;
struct Solve
{
int n,opt[N];
vector<int>nx[N<<2];
void up(int k)
{
assert(nx[k].empty());
int lc=k<<1,rc=k<<1|1,tmp=0;
for(int i=0;i<(int)nx[rc].size();i++)
{
while(tmp<(int)nx[lc].size()&&nx[lc][tmp]<=nx[rc][i]-1-i)
nx[k].push_back(nx[lc][tmp]+i),tmp++;
nx[k].push_back(nx[rc][i]);
}
while(tmp<(int)nx[lc].size())
nx[k].push_back(nx[lc][tmp]+(int)nx[rc].size()),tmp++;
}
void update(int k,int l,int r,int x)
{
if(l==r)
{
nx[k].push_back(x);
return;
}
int mid=(l+r)>>1;
if(n<=mid) update(k<<1,l,mid,x);
else update(k<<1|1,mid+1,r,x);
if(r==n) up(k);
}
void update(int tim,int x)
{
opt[++n]=tim;
update(1,1,Q,x);
}
int rollback(int k,int x)
{
int tmp=lower_bound(nx[k].begin(),nx[k].end(),x)-nx[k].begin();
assert(tmp==(int)nx[k].size()||x!=nx[k][tmp]);
return x-tmp;
}
int qtim(int k,int l,int r,int &x)
{
if(r<=n)
{
int tmp=lower_bound(nx[k].begin(),nx[k].end(),x)-nx[k].begin();
if(tmp==(int)nx[k].size()||x!=nx[k][tmp])
{
x-=tmp;
return 0;
}
}
if(l==r) return l;
int mid=(l+r)>>1;
if(n>mid)
{
int tmp=qtim(k<<1|1,mid+1,r,x);
if(tmp) return tmp;
}
return qtim(k<<1,l,mid,x);
}
int qtim(int x)
{
if(!n) return 0;
return opt[qtim(1,1,Q,x)];
}
void query(int k,int l,int r,int st,int &x)
{
if(st<=l&&r<=n)
{
x=rollback(k,x);
return;
}
int mid=(l+r)>>1;
if(n>mid) query(k<<1|1,mid+1,r,st,x);
if(st<=mid) query(k<<1,l,mid,st,x);
}
void query(int tim,int &x)
{
int st=upper_bound(opt+1,opt+n+1,tim)-opt;
if(st<=n) query(1,1,Q,st,x);
}
}tx,ty;
void query(int &x,int &y)
{
int xtim=tx.qtim(x),ytim=ty.qtim(y);
if(!xtim&&!ytim)
{
tx.query(0,x),ty.query(0,y);
return;
}
if(xtim>ytim)
{
x=xtim;
ty.query(xtim,y);
}
else
{
y=ytim;
tx.query(ytim,x);
}
}
int main()
{
Q=read();
int lx=0,ly=0;
for(int tim=1;tim<=Q;tim++)
{
int opt=read();
if(opt==1) ty.update(tim,read()+lx+ly);
if(opt==2) tx.update(tim,read()+lx+ly);
if(opt==3)
{
int x=read()+lx+ly,y=read()+lx-ly;
query(x,y);
printf("%d %d\n",lx=x,ly=y);
}
}
return 0;
}
标签:Inserting,ch,格子,int,线段,Lines,XSY3551,坐标,区间
From: https://www.cnblogs.com/ez-lcw/p/16841020.html