WBLT 及 ULR#3F 相关 Trick 笔记
为什么你必须学习 ULR#3F 因为它是区间查询复合函数的新优选
WBLT
定义
LeafyTree 为信息存储在叶子节点上的树,WBLT 为重量平衡的 LeafyTree。
设节点
明显地,对于任意正整数个数的节点,都可以以其为叶子构造一颗 WBLT(直接采用线段树的构造方式)。
定义一个节点
son[o][0/1]:左右儿子 ls rs
w[o]:重量
val[o]:子树信息并
定义函数 newnode() 为新建一个节点,若有参数则为复制此节点;
定义 delnode(o) 为删除回收节点 o。
WBLT 在维护过程中会不断新增和删除节点,普通 WBLT 需要使用空间回收来保证空间复杂度正确。
int newnode(int o1=0){
o=top?stk[top--]:m;
w[o]=o1?w[o1]:1,tg[o]=tg[o1];
son[o][0]=son[o1][0];
son[o][1]=son[o1][1];
return o;
}
void delnode(int o){stk[++top]=o;}
定义 cmpr(x,y) 为比较重量
int cmpr(int x,int y){
if(y*3ll<x)return 1;
if(x*3ll<y)return 2;
return 0;
}
定义 pup(o) 为更新
void pup(int o){w[o]=w[ls]+w[rs];}
通过合并/分裂维护 WBLT
定义函数 join(x,y),cut(x):
int join(int x,int y){
int o=newnode();
ls=x,rs=y;pup(o);
return o;
}
pii cut(int o){
int x=ls,y=rs;
delnode(o);
return mkp(x,y);
}
类似 FHQTreap,WBLT 采用递归合并,合并的过程为:
若二子树平衡,则直接合并。
否则按照两种形态合并:
以右边大为例,先拆分
否则拆分
合并两棵树的复杂度为
int mer(int x,int y){
if(!x||!y)return x|y;
int op=cmpr(w[x],w[y]);
if(op==1){
auto [a,b]=cut(x);
if(!cmpr(w[a],w[b]+w[y]))return mer(a,mer(b,y));
auto [c,d]=cut(b);
return mer(mer(a,c),mer(d,y));
}
if(op==2){
auto [a,b]=cut(y);
if(!cmpr(w[x]+w[a],w[b]))return mer(mer(x,a),b);
auto [c,d]=cut(a);
return mer(mer(x,c),mer(d,b));
}
return join(x,y);
}
分裂过程类似线段树分裂,复杂度
pii split(int o,int k){//按照重量分裂
if(!o)return mkp(0,o);
if(w[o]<=k)return mkp(o,0);
int x,y;
if(k<=w[ls]){
tie(x,y)=split(ls,k);
y=mer(y,rs);
}else{
tie(x,y)=split(rs,k=w[ls]);
x=mer(ls,x);
}
cut(o);
return mkp(x,y);
}
如果要实现功能函数,如存储标记等,则每次 cut 时必须把标记 pushdown 到子节点。
WBLT 的可持久化
在以上过程中删去 delnode 即可,每次修改必须新建节点。
inline int newnode(int o=0){
++m,w[m]=o?w[o]:1,tg[m]=tg[o];
son[m][0]=ls,son[m][1]=rs;
return m;
}
inline int Add(int o,int x){
o=newnode(o);
tg[o]=func(o,x);//更新 tag
return o;
}
inline pii cut(int o){
int x=ls,y=rs;
if(tg[o])
x=Add(x,tg[o]),y=Add(y,tg[o]);
return mkp(x,y);
}
注意可持久化的 split 有比标记下传常数更小的写法:
int splitL(int o,uint k){//重量 split 左边部分
if(!k)return 0;
if(w[o]<=k)return o;
int x;
if(k<=w[ls])x=splitL(ls,k);
else x=mer(ls,splitL(rs,k-w[ls]));
if(tg[o])x=Add(x,tg[o]);//打标记不用下传
return x;
}
WBLT 维护区间复制
直接根据可持久化维护区间复制即可。
空间优化:引用计数
这个内容是学弟@Cyril想想告诉我的,他说这个空间是线性的,我不会证。原文链接。
仅实现区间复制不需要令所有节点可持久化,记
若一个节点的
当一个节点被 delnode 时其
带区间复制 WBLT 代码:
namespace WBLT{
#define ls son[o][0]
#define rs son[o][1]
const int M=2e7+5;
Ele val[M];//维护信息
int w[M];//weight
int ref[M];//引用计数
int son[M][2],tot=0;
int stk[M],top=0;
int newnode(Ele v=(Ele){0,0,0,0}){
int o=top?stk[top--]:++tot;
val[o]=v,w[o]=1;
ref[o]=1,ls=rs=0;
return o;
}
void delnode(int &o){
if(--ref[o])return;
delnode(ls),delnode(rs);
stk[++top]=o,o=0;
}
void pup(int o){
val[o]=val[ls]+val[rs],w[o]=w[ls]+w[rs];
}
int cmpr(int x,int y){
if(x>y*3)return 1;
if(y>x*3)return 2;
return 0;
}
int join(int x,int y){
if(!x||!y)return x|y;
int o=newnode();
ls=x,rs=y,pup(o);
return o;
}
pii cut(int &o){
int x=ls,y=rs;
++ref[x],++ref[y];
delnode(o);
return mkp(x,y);
}
int mer(int x,int y){
if(!x||!y)return x|y;
int op=cmpr(w[x],w[y]),a,b,c,d;
if(op==1){
tie(a,b)=cut(x);
if(!cmpr(w[a],w[b]+w[y]))return mer(a,mer(b,y));
tie(c,d)=cut(b);return mer(mer(a,c),mer(d,y));
}
if(op==2){
tie(a,b)=cut(y);
if(!cmpr(w[x]+w[a],w[y]))return mer(mer(x,a),b);
tie(c,d)=cut(a);return mer(mer(x,c),mer(d,b));
}
return join(x,y);
}
void copy(int o){++ref[o];}
void query(int o,int lt,int rt,int &x){
if(!o)return;
int l=val[o].tl,r=val[o].tr;
if(r<lt||l>rt)return;
if(l>=lt&&r<=rt){
x=(1ll*val[o].k*x+val[o].b)%mod;
return;
}
query(ls,lt,rt,x),query(rs,lt,rt,x);
}
#undef ls
#undef rs
}
区间复制实现倍增建树
建出大小为 root=mer(root,root) 操作
如果你想一开始做一个带标号的,那就在复制的时候给右边的一份打上加标记。
维护区间分段函数
下面的题目中每次执行操作都和当前的某个值的大小有关,我们称这个值为关键值。
关键值不变
P11721 [清华集训 2014] 玄学
每次的操作是对于一段关键值修改答案,而不改变关键值本身。
序列是动态构造的,由于关键值始终不变,则对关键值建立线段树。
由于每次查询一段操作时间上的操作,则内层嵌套平衡树,每次修改操作给
标记下传时需要合并两个标记,则直接把父亲节点的标记接在子节点的树后,使用 WBLT 维护区间合并即可。
struct Ele{
int tl,tr,k,b;//操作时间,操作系数
friend Ele operator +(const Ele &lf,const Ele &rf){
return (Ele){lf.tl,rf.tr,int(1ll*lf.k*rf.k%mod),int((1ll*lf.b*rf.k+rf.b)%mod)};
}
};
namespace WBLT{//本题不存在区间修改,即不需要新建节点
#define ls son[o][0]
#define rs son[o][1]
const int M=2e7+5;
Ele val[M];//维护信息
int w[M];//weight
int ref[M];//引用计数
int son[M][2],tot=0;
int stk[M],top=0;
int newnode(Ele v=(Ele){0,0,0,0}){
int o=top?stk[top--]:++tot;
val[o]=v,w[o]=1;
ref[o]=1,ls=rs=0;
return o;
}
void delnode(int &o){
if(--ref[o])return;
delnode(ls),delnode(rs);
stk[++top]=o,o=0;
}
void pup(int o){
val[o]=val[ls]+val[rs],w[o]=w[ls]+w[rs];
}
int cmpr(int x,int y){
if(x>y*3)return 1;
if(y>x*3)return 2;
return 0;
}
int join(int x,int y){
if(!x||!y)return x|y;
assert(!cmpr(w[x],w[y]));
int o=newnode();
ls=x,rs=y,pup(o);
return o;
}
pii cut(int &o){
assert(w[o]>1);
int x=ls,y=rs;
++ref[x],++ref[y];
delnode(o);
return mkp(x,y);
}
int mer(int x,int y){
if(!x||!y)return x|y;
int op=cmpr(w[x],w[y]),a,b,c,d;
if(op==1){
tie(a,b)=cut(x);
if(!cmpr(w[a],w[b]+w[y]))return mer(a,mer(b,y));
tie(c,d)=cut(b);return mer(mer(a,c),mer(d,y));
}
if(op==2){
tie(a,b)=cut(y);
if(!cmpr(w[x]+w[a],w[y]))return mer(mer(x,a),b);
tie(c,d)=cut(a);return mer(mer(x,c),mer(d,b));
}
return join(x,y);
}
void bal(int &o){
if(w[o]==1)return;
if(cmpr(w[ls],w[rs])){
int a,b;tie(a,b)=cut(o);
o=mer(a,b);
}
}
void copy(int o){++ref[o];}
void query(int o,int lt,int rt,int &x){
if(!o)return;
int l=val[o].tl,r=val[o].tr;
if(r<lt||l>rt)return;
if(l>=lt&&r<=rt){
x=(1ll*val[o].k*x+val[o].b)%mod;
return;
}
query(ls,lt,rt,x),query(rs,lt,rt,x);
}
#undef ls
#undef rs
}
int a[N],n,m,q,Ans=0;
namespace sgt{
#define ls (o<<1)
#define rs (ls|1)
#define mid ((l+r)>>1)
const int M=4e5+5;
int root[M];//标记(平衡树)
void Mer(int o,int rtx){
WBLT::copy(rtx);
root[o]=WBLT::mer(root[o],rtx);
}
void psd(int o){
if(root[o]){
Mer(ls,root[o]),Mer(rs,root[o]);
WBLT::delnode(root[o]);
root[o]=0;
}
}
void ins(int o,int l,int r,int lt,int rt,int rtx){
if(l>=lt&&r<=rt)return Mer(o,rtx);
psd(o);
if(lt<=mid)ins(ls,l,mid,lt,rt,rtx);
if(rt> mid)ins(rs,mid+1,r,lt,rt,rtx);
}
int query(int o,int l,int r,int p,int lt,int rt){
if(l==r){
int x=a[p];WBLT::query(root[o],lt,rt,x);
return x;
}
psd(o);
return (p<=mid?query(ls,l,mid,p,lt,rt):query(rs,mid+1,r,p,lt,rt));
}
#undef ls
#undef rs
#undef mid
关键值改变
P8264 [Ynoi Easy Round 2020] TEST_100
每次的操作是对于一段关键值修改答案并改变关键值本身。
对比上一题,本题关键值存在移动,答案不再保持线性段,考虑倒序维护:
假设我们总是询问操作编号在
考虑操作编号不再是后缀,此时外层套一个线段树,内层先使用前文所述倍增建树,然后倒序模拟操作即可。
为什么不正序维护?正序维护要求把两个不同答案合并到同一个地方,这是一个对位的操作,不方便维护;而倒序下正好相反,是把一个相同的答案复制一份到别的地方,可以用平衡树快速维护。
代码懒了。
全局平衡二叉树
这个东西结构上基本等于静态 TopTree(重链上平衡 compress,轻边 rake)。
树链剖分后对于每条重链进行重量平衡:即每个节点重新记录
较为简单的说,就是根据重量重新平衡了每条重链的线段树(普通二叉树形式)。
明显地,两部分平衡之后,每一部分都只会跳
建树代码:
vector<int>e[N];
int fa[N],d[N],sz[N],hs[N],n;
void dfs(int u,int Fa,int D){//重剖
fa[u]=Fa,d[u]=D;
for(auto v:e[u])if(v!=Fa){
dfs(v,u,D+1),sz[u]+=sz[v];
if(sz[v]>=sz[hs[u]])hs[u]=v;
}
}
int ch[N][2];//二叉树重边
int up[N];//二叉树父亲
int dfn[N];//链上编号
int L[N],R[N];//二叉树子树的最小/最大 dfn
int sum[N],stk[N],top=0;
【节点信息】val[N];
int buildc(int l,int r,int Fa){
int md=(l==r)?l:lower_bound(sum+l,sum+r+1,(sum[l]+sum[r])>>1)-sum;
int u=stk[md];
up[u]=Fa,L[u]=R[u]=dfn[u];
if(l<md){
ch[u][0]=buildc(l,md-1,u),L[u]=L[ch[u][0]];
【合并左边节点信息】
}
【处理当前节点信息】
if(r>md){
ch[u][1]=buildc(md+1,r,u),R[u]=R[ch[u][1]];
【合并右边节点信息】
}
return u;
}
void build(){
dfs(1,0,0);
for(int st=1;st<=n;st++)if(st!=hs[fa[st]]){
top=0;
for(int x=st;x;x=hs[x])stk[++top]=x,dfn[x]=top;
buildc(1,top,0);
}
}
DAG 链剖分
对于本题的 DAG,每个点选取子节点中路径条数(
写成代码即:
for(int i=1+n;i<=m;i++){
bool op=(sz[son[i][0]]<sz[son[i][1]]);
if(sz[i]>sz[pre[son[i][op]]])pre[son[i][op]]=i;
}
for(int i=1;i<=m;i++)if(pre[i])hs[pre[i]]=i;
类似全局平衡二叉树形态的 DAG 剖分
对于符合条件的 DAG 同样对于每条链建立类似结构:
up[u]:二叉树结构父亲
dfn[u]:u 在链上的顺序
ch[u][0/1]:u 在二叉树结构上的左右儿子
L[u],R[u]:(二叉树)子树所占链上区间
int buildc(int l,int r,int Fa){
int md=((l==r)?l:lower_bound(sum+l,sum+r+1,(sum[l-1]+sum[r])>>1)-sum);
int u=stk[md];
up[u]=Fa,L[u]=l,R[u]=r,sm[u]=(Node){0,1,0};
if(l<md)ch[u][0]=buildc(l,md-1,u),sm[u]=sm[u]+sm[ch[u][0]];
if(hs[u]==son[u][1])sm[u]=sm[u]+nd[son[u][0]];
if(md<r)ch[u][1]=buildc(md+1,r,u),sm[u]=sm[u]+sm[ch[u][1]];
return u;
}
void build(int n){
for(int i=1+n;i<=m;i++){
bool op=(sz[son[i][0]]<sz[son[i][1]]);
if(sz[i]>sz[pre[son[i][op]]])pre[son[i][op]]=i;
}
for(int i=1;i<=m;i++)if(pre[i])hs[pre[i]]=i;
for(int st=m;st;st--)if(!pre[st]){
top=0;
for(int x=st;x;x=hs[x])
stk[++top]=x,dfn[x]=top;
sum[top]=sz[st];
for(int i=1;i<top;i++)sum[i]=sz[st]-sz[stk[i+1]];
buildc(1,top,0);
}
}
在剖分结构上向下跳链查询
直接套用全局平衡二叉树的做法,在每条重链上跳并寻找跳轻边的点。
注意这道题我们要求的是一条右边的链以及其左边的答案。
本题重链向下的
代码见后文。
解决方法
官方题解。
这道题目的关键值
外层维护可持久化 WBLT,倒序扫描时,每次操作相当于分裂一段再合并。
内层维护支持复制树形数据结构,先继续采用 WBLT,每次分裂时把
考虑一次查询操作:从外层树的某个节点开始向下查找:
每次遇到 tag 就在内层树上查找。
当然我们所有的查询都是一个前缀(取出的根是
当前预处理
考虑所有内层树的结构其实是一个 DAG,所以对于内层树,直接使用不平衡的 LeafyTree 代替,现在合并变成了
内层树的节点个数为
下面实现的 DAG 链剖分大量cpy参考了wzc_IOI_czw 的代码。
套用上文的 DAG 链剖分,由于链剖分的复杂度是节点数线性的,则预处理复杂度降低到了
前面的实现是简易的,考虑 DAG 链剖分的实现:
内层 DAG 每个点有数据
我们从某个点开始向下查询“子树”中操作时间在
每次查询时,要定位靠右边的链并获取链左侧所有子树的信息并,这个是容易在轻子树信息中维护的。
对于一条重链,向下时每个点的
接着查询当前链节点并跳轻边到子节点上,不断循环本操作即可。
内层树+外层跳链的复杂度依然满足类似全局平衡二叉树的性质,则查询复杂度为
疑问:我的 DAG 链剖分部分代码基本是由上文链接中提交改来的,官解中“由于全局平衡的 DAG 剖分需要使用 finger search” 一句并没有理解,所以我代码的复杂度也许有误。
具体实现(目前是最优解):
#pragma GCC optimize("unroll-loops")
#include "bits/stdc++.h"
using namespace std;
template<typename T>void in(T &n){n=0;char c=getchar();bool flag=0;for(;c<'0'||c>'9';c=getchar())if(c=='-')flag=1;for(;c>='0'&&c<='9';c=getchar())(n*=10)+=(c^48);if(flag)n=-n;}
int wlsk[45];int wltp;
template<typename T>void out(T n,char c=0){if(n==0){putchar('0');if(c)putchar(c);return;}if(n<0)putchar('-'),n=-n;while(n) wlsk[++wltp]=(n%10),n/=10;while(wltp) putchar(wlsk[wltp--]^48);if(c)putchar(c);}
#define pii pair<int,int>
#define mkp make_pair
#define fir first
#define sec second
const int N=2e5+5,M=1e7+5,M2=3e7+5;
const uint inf=2147483647;
struct Node{
uint y,a,b;
inline friend Node operator +(const Node &lf,const Node &rf){return (Node){lf.y+rf.y,lf.a*rf.a,rf.b+rf.a*lf.b};}
}nd[M];
int n;
namespace DAG{
Node sm[M];
int son[M][2],tl[M],tr[M],sz[M],m;
int pre[M],hs[M];
int dfn[M],up[M],ch[M][2],L[M],R[M];
int stk[M],sum[M],top;
//up 的含义更改为向上最近的覆盖链后缀的节点,减小常数(这里你改为父亲/二叉树根都行)
inline int mer(int x,int y){
if(!x||!y)return x|y;
m++;
son[m][0]=x,son[m][1]=y;
tl[m]=tl[x],tr[m]=tr[y];
nd[m]=nd[x]+nd[y],sz[m]=sz[x]+sz[y];
return m;
}
int buildc(int l,int r,int Fa){
int md=((l==r)?l:lower_bound(sum+l,sum+r+1,(sum[l-1]+sum[r])>>1)-sum);
int u=stk[md];if(r==top)Fa=u;
up[u]=Fa,L[u]=l,R[u]=r,sm[u]=(Node){0,1,0};
if(l<md)
ch[u][0]=buildc(l,md-1,Fa),sm[u]=sm[u]+sm[ch[u][0]];
if(hs[u]==son[u][1])
sm[u]=sm[u]+nd[son[u][0]];//轻子树信息
if(md<r)
ch[u][1]=buildc(md+1,r,Fa),sm[u]=sm[u]+sm[ch[u][1]];
return u;
}
void build(int n){
for(int i=1+n;i<=m;i++){
bool op=(sz[son[i][0]]<sz[son[i][1]]);
if(sz[i]>sz[pre[son[i][op]]])pre[son[i][op]]=i;
}
for(int i=1;i<=m;i++)if(pre[i])hs[pre[i]]=i;
for(int st=m;st;st--)if(!pre[st]){
top=0;
for(int x=st;x;x=hs[x])
stk[++top]=x,dfn[x]=top;
sum[top]=sz[st];
for(int i=1;i<top;i++)sum[i]=sz[st]-sz[stk[i+1]];
buildc(1,top,0);
}
}
Node query(int u,int lt,int rt){
if(!u)return (Node){0,1,0};
if(lt<=L[u]&&R[u]<=rt)return sm[u];
Node res=(Node){0,1,0};
if(lt<dfn[u])res=res+query(ch[u][0],lt,rt);
if(lt<=dfn[u]&&rt>=dfn[u]&&(hs[u]==son[u][1]))res=res+nd[son[u][0]];
if(rt>dfn[u])res=res+query(ch[u][1],lt,rt);
return res;
}//二叉树上查询
Node qry(int u,int R){//定位右侧链查左侧答案
if(R<tl[u])return (Node){0,1,0};
if(tr[u]<=R)return nd[u];
Node res=(Node){0,1,0};
while(tl[u]<=R){
int v=up[u],ed=u;
while(v){//tl up,tr down
//定位重链上最后一点
if(tl[v]<=R&&tr[v]>R)ed=v,v=ch[v][1];
else v=ch[v][0];
}
if(dfn[u]<dfn[ed])res=res+query(up[u],dfn[u],dfn[ed]-1);
if(ed<=n)break;//到叶子了
if(tr[son[ed][0]]<=R)res=res+nd[son[ed][0]],u=son[ed][1];
else u=son[ed][0];//跳链
}
return res;
}
}
#define ls son[o][0]
#define rs son[o][1]
namespace WBLT{
int son[M2][2],tg[M2],m,tm;
uint w[M2];
inline void reft(){tm=m;}
inline int cmpr(int x,int y){
if(y*3ll<x)return 1;
if(x*3ll<y)return 2;
return 0;
}
inline int newnode(int o=0){
if(o>tm)return o;
++m,w[m]=o?w[o]:1,tg[m]=tg[o];
son[m][0]=ls,son[m][1]=rs;
return m;
}
inline void pup(int o){w[o]=w[ls]+w[rs];}
inline int Add(int o,int x){o=newnode(o);tg[o]=DAG::mer(x,tg[o]);return o;}
inline int join(int x,int y){
int o=newnode();
ls=x,rs=y;pup(o);
return o;
}
inline pii cut(int o){
int x=ls,y=rs;
if(tg[o])
x=Add(x,tg[o]),y=Add(y,tg[o]);
return mkp(x,y);
}
int mer(int x,int y){
if(!x||!y)return x|y;
int op=cmpr(w[x],w[y]);
if(op==1){
auto [a,b]=cut(x);
if(!cmpr(w[a],w[b]+w[y]))return mer(a,mer(b,y));
auto [c,d]=cut(b);
return mer(mer(a,c),mer(d,y));
}
if(op==2){
auto [a,b]=cut(y);
if(!cmpr(w[x]+w[a],w[b]))return mer(mer(x,a),b);
auto [c,d]=cut(a);
return mer(mer(x,c),mer(d,b));
}
return join(x,y);
}
int splitL(int o,uint k){
if(!k)return 0;
if(w[o]<=k)return o;
int x;
if(k<=w[ls])x=splitL(ls,k);
else x=mer(ls,splitL(rs,k-w[ls]));
if(tg[o])x=Add(x,tg[o]);
return x;
}
int splitR(int o,uint k){
if(!k)return o;
if(w[o]<=k)return 0;
int x;
if(k<=w[ls])x=mer(splitR(ls,k),rs);
else x=splitR(rs,k-w[ls]);
if(tg[o])x=Add(x,tg[o]);
return x;
}
Node qry(int o,int X,int rt){
Node res=(Node){0,1,0};
while(o){
if(tg[o])res=res+DAG::qry(tg[o],rt);
if(w[o]==1)break;
X<=w[ls]?o=ls:(X-=w[ls],o=rs);
}
return res;
}
}
#undef ls
#undef rs
int root[N],LT[N],q;
int xo[N],yo[N];
uint ao[N],bo[N];
uint LST;
int main(){
in(n),in(q);
for(int i=1;i<=n;i++)
in(xo[i]),in(yo[i]),in(ao[i]),in(bo[i]);
for(int i=1;i<=n;i++)
LT[n+1]=min(0,int(LT[n+1]+yo[i]));
root[n+1]=WBLT::newnode(),WBLT::reft();
for(int b=1;b<inf;b<<=1)
root[n+1]=WBLT::mer(root[n+1],root[n+1]),WBLT::reft();
DAG::m=n,nd[0]=(Node){0,1,0};
for(int i=1;i<=n;i++){
DAG::tl[i]=DAG::tr[i]=i;
DAG::sz[i]=1;
nd[i]=(Node){yo[i],ao[i],bo[i]};
}
for(int x,y,i=n;i;i--){
y=WBLT::splitR(root[i+1],xo[i]-LT[i+1]+1);
x=WBLT::splitL(root[i+1],xo[i]+yo[i]-LT[i+1]+1);
root[i]=WBLT::mer(WBLT::Add(x,i),y);
WBLT::reft(),LT[i]=LT[i+1]-yo[i];
}
DAG::build(n);
uint X,lt,rt;
long long TX,TL,TR;
while(q--){
in(TL),in(TR),in(TX);
X=TX,lt=TL,rt=TR,X^=LST,lt^=LST,rt^=LST;
Node Ans=WBLT::qry(root[lt],X-LT[lt]+1,rt);
out(int(X+Ans.y),' '),out(Ans.a,' '),out(Ans.b,'\n');
LST=Ans.a^Ans.b;
}
return 0;
}
解决一个有趣的题目是一个有趣的过程,能学到很多有趣的东西。