NFLSOJ #12461 -「NOIP2021模拟赛1001杭二」树套树(线段树合并+兔队线段树)
首先考虑一条链的情况。BST 这个东西不好维护,思考如何转化为我们熟悉的数据结构。注意到一棵 BST 等价于一棵按照权值排序之后,以时间 \(t\) 为键值建立的笛卡尔树,其中每个点的键值大于其父亲(根节点除外),而看到笛卡尔树我们可以很自然地想到单调栈,因此我们考虑将操作离线下来扫描线,我们用线段树维护一个以键值为下标,时间为值的线段树,也就是说我们对时间轴、下标、值做一个置换。这样每次操作可以写成,往序列中某个位置插入一个数 / 删除一个数 / 查询序列建出来的 Cartesian tree 中,某个点祖先的权值之和。根据笛卡尔树那套理论,一个点在笛卡尔树上所有祖先,等于从这个点向左向右分别跳,每次跳到离这个点最近的,且值小于这个点的值的位置,经过的所有点的集合,也就是说,对于一次查询,我们假设时间为 \(t\),查询的值为 \(v\),那么我们就要从 \(v\) 开始,不断向左跳,每次找到在它左边离它最近,且值小于 \(t\) 的位置,然后将 \(t\) 赋为这样的位置上的值。对于右边也重复一遍同样的操作。
考虑如何维护之,上面操作等价于动态地对于每个点维护在它前面离其最近的、且值小于该点的值的位置 \(pre_i\),以及它后面的离其最近的、且值小于该点的值的位置 \(nxt_i\),这个直接维护起来不容易,因此考虑直接在询问时向后跳 \(pre/nxt\) 来计算答案,以 \(nxt\) 为例,我们考虑将待查询区间(也就是一段后缀)拆成若干个小区间,然后对于每个小区间在线段树上表示的节点,我们从这样的节点开始 DFS,每次我们考察其左子树的最小值,如果左子树最小值 \(<\) 当前的 \(t\),那么先进左子树后进右子树,否则直接进右子树,如果递归到了叶子节点那么就考察叶子节点的值与当前 \(t\) 的大小关系,如果 \(t>\) 叶子节点的值就令答案加上当前节点的值,正确性显然,时间复杂度也显然是错的——可以被卡掉平方(
考虑优化,注意到如果我们递归了左子树,那么从左子树出来时,\(t\) 肯定等于左节点的最小值,因此我们考虑设一个 \(sum_k\),\(sum_{rs_k}\) 表示当递归到 \(k\) 时,\(t=\) 左子树最小值时递归右子树得到的和,其中 \(rs_k\) 表示 \(k\) 的右儿子。这样 pushup
时需要在右子树内调用一遍 query
函数,这有点类似于 P4198 楼房重建——二者需要在 pushup
时调用其他函数。这样 query
时,如果需要进入左子树,就直接进入左子树递归,右子树的贡献直接加上 \(sum_{rs_k}\)。粉兔神仙为此写过一篇论文《从<楼房重建>出发浅谈一类使用线段树维护前缀最大值的算法》 专门介绍了这一类线段树,所以这类线段树又被称为“兔队线段树”(wjz:感觉在迫害小粉兔)。这样,由于在修改时,pushup
中还要一个 \(\log\) 的常数,总复杂度就是 \(n\log^2n\)。对于 \(pre\) 的情况——直接把序列翻转一下就和 \(suf\) 的情况一样了,可以写一个结构体 struct solver
解决两种情况。
最后思考如何扩展到树上,其实如果你理解了链的情况,那么树的情况就很 trivial 了。发现这个模型很像 P4556 Vani有约会 雨天的尾巴 /【模板】线段树合并,按照那题的套路树上差分一下,然后一遍 DFS 推标记即可。复杂度还是 \(n\log^2n\)。
const int MAXN=2e5;
const int MAXP=MAXN<<5;
const int LOG_N=18;
const int INF=0x3f3f3f3f;
int n,qu,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0;u32 key[MAXN+5];
struct qry{int opt,x,y;u32 k;} q[MAXN+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int fa[MAXN+5][LOG_N+2],dep[MAXN+5];
void dfs0(int x,int f){
fa[x][0]=f;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;
dep[y]=dep[x]+1;dfs0(y,x);
}
}
int getlca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=LOG_N;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
if(x==y) return x;
for(int i=LOG_N;~i;i--) if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
struct segtree{
int a[MAXN+5],tim[MAXN+5],rt[MAXN+5],ncnt=0;
struct node{int ch[2],sum,mn,cnt;} s[MAXP+5];
int calc(int k,int l,int r,int t){
if(!k) return 0;if(l==r) return (s[k].mn<t)?a[l]:0;
int mid=l+r>>1;
if(!s[k].ch[0]||s[s[k].ch[0]].mn>=t) return calc(s[k].ch[1],mid+1,r,t);
return calc(s[k].ch[0],l,mid,t)+s[s[k].ch[1]].sum;
}
void pushup(int k,int l,int r){
int mid=l+r>>1;
s[k].mn=min((s[k].ch[0])?s[s[k].ch[0]].mn:INF,(s[k].ch[1])?s[s[k].ch[1]].mn:INF);
if(s[k].ch[0]) s[s[k].ch[1]].sum=calc(s[k].ch[1],mid+1,r,(s[k].ch[0])?s[s[k].ch[0]].mn:INF);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;int z=++ncnt,mid=l+r>>1;
if(l==r) return s[z].cnt=s[x].cnt+s[y].cnt,s[z].mn=min(s[x].mn,s[y].mn),z;
s[z].ch[0]=merge(s[x].ch[0],s[y].ch[0],l,mid);
s[z].ch[1]=merge(s[x].ch[1],s[y].ch[1],mid+1,r);
return pushup(z,l,r),z;
}
void modify(int &k,int l,int r,int p,int v){
if(!k) k=++ncnt;
if(l==r) return s[k].cnt+=v,s[k].mn=(s[k].cnt)?tim[l]:INF,void();
int mid=l+r>>1;
if(p<=mid) modify(s[k].ch[0],l,mid,p,v);
else modify(s[k].ch[1],mid+1,r,p,v);
pushup(k,l,r);
}
int curmn=INF;
int query(int k,int l,int r,int ql,int qr){
if(!k) return 0;
if(ql<=l&&r<=qr){
if(s[k].mn>=curmn) return 0;
int res=calc(k,l,r,curmn);
curmn=s[k].mn;//printf("%d\n",curmn);
return res;
} int mid=l+r>>1;
if(qr<=mid) return query(s[k].ch[0],l,mid,ql,qr);
else if(ql>mid) return query(s[k].ch[1],mid+1,r,ql,qr);
else return query(s[k].ch[0],l,mid,ql,mid)+query(s[k].ch[1],mid+1,r,mid+1,qr);
}
int query(int u,int v,int l,int r){
curmn=v;return query(rt[u],1,qu,l,r);
}
} L,R;
vector<int> add[MAXN+5],del[MAXN+5],qry[MAXN+5];
u32 res[MAXN+5];
void modify(int u,int x,int v){
// printf("modify %d %d %d\n",u,x,v);
L.modify(L.rt[u],1,qu,qu-x+1,v);
R.modify(R.rt[u],1,qu,x,v);
}
int query(int u,int p,int t){
// printf("query %d %d %d\n",u,p,t);
return L.query(u,t,qu-p+1,qu)+R.query(u,t,p,qu);
}
void dfs(int x,int f){
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;dfs(y,x);
L.rt[x]=L.merge(L.rt[x],L.rt[y],1,qu);
R.rt[x]=R.merge(R.rt[x],R.rt[y],1,qu);
}
for(int id:add[x]) modify(x,q[id].k,1);
for(int id:del[x]) modify(x,q[id].k,-1);
for(int id:qry[x]) res[id]=query(x,q[id].k,id);
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d%d",&n,&qu);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
dfs0(1,0);for(int i=1;i<=LOG_N;i++) for(int j=1;j<=n;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
for(int i=1;i<=qu;i++){
scanf("%d",&q[i].opt);
if(q[i].opt==1) scanf("%d%d%u",&q[i].x,&q[i].y,&q[i].k);
else scanf("%d%u",&q[i].x,&q[i].k);
key[i]=q[i].k;
} sort(key+1,key+qu+1);
for(int i=1;i<=qu;i++) q[i].k=lower_bound(key+1,key+qu+1,q[i].k)-key;
for(int i=1;i<=qu;i++){
L.a[qu-q[i].k+1]=key[q[i].k];L.tim[qu-q[i].k+1]=i;
R.a[q[i].k]=key[q[i].k];R.tim[q[i].k]=i;
if(q[i].opt==1){
int lc=getlca(q[i].x,q[i].y);
add[q[i].x].pb(i);add[q[i].y].pb(i);
del[lc].pb(i);del[fa[lc][0]].pb(i);
// printf("%d\n",lc);
} else qry[q[i].x].pb(i);
} dfs(1,0);
// for(int i=1;i<=qu;i++) printf("%d %d %d %d\n",L.a[i],R.a[i],L.tim[i],R.tim[i]);
for(int i=1;i<=qu;i++) if(q[i].opt==2) printf("%u\n",res[i]);
return 0;
}