题解 CF226E Noble Knight's Path
题目描述
给⼀棵树,现在有两种操作:
-
标记某⼀个节点;
-
找到路径 $a$ 至 $b$ 中在第 $y$ 次操作到当前操作之间没有被标记的第 $k$ 个节点。
保证每个节点只会被标记一次。
思路
首先用可持久化线段树维护树剖,对于修改操作直接修改即可,对于查询,直接在树剖上跳,如果未标记节点小于 $k$ 输出 -1,否则输出答案。
实现
这道题个人认为最大的难点是在代码实现上,以及本人代码实现较差。
首先进行树剖,维护使用可持久化线段树。
对于修改操作,直接根据 dfs 序修改即可。
对于查询操作,首先分为两部分:从 $a$ 至 $lca$ 的部分和从 $lca$ 至 $b$ 的部分,特判 $a$ 或 $b$ 为 $lca$ 的情况。
对于每个被剖出来的区间,我们可以放进一个容器里,如果是下至上就可以用队列,如果上至下就可以用栈,当然用 vector
然后再 reserve
一下也可以。
然后就判断第 $k$ 个没打标记的在哪个区间内。
有两种方法可以判断第 $k$ 个在区间中的具体位置:
-
在线段树上特定区间二分,能写,但是个人认为容易写挂;
-
直接二分位置,然后判断是否合法。
注意此时要根据上行或下行判断是区间左边第 $k$ 个还是区间右边第 $k$ 个。
以及由于是被分开的询问还是开区间,使用倍增找出 $lca$ 的儿子。
具体实现细节在代码中。
时间复杂度 $O(n\log^2n)$。
喜闻乐见的代码
#include<assert.h>
#include<iostream>
#include<queue>
#include<stack>
using namespace std;
struct Persistent_Segment_Tree//可持久化线段树,不用多说
{
int cnt;
struct sss
{
int l,r,sum;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define sum(x) tree[x].sum
}tree[5000005];
int build(int l,int r)
{
int root=++cnt;
if(l==r)return root;
int mid=(l+r)>>1;
l(root)=build(l,mid);
r(root)=build(mid+1,r);
return root;
}
int change(int x,int l,int r,int p)
{
int root=++cnt;
tree[root]=tree[x];
if(l==r)return sum(root)=1,root;
int mid=(l+r)>>1;
if(p<=mid)l(root)=change(l(root),l,mid,p);
else r(root)=change(r(root),mid+1,r,p);
sum(root)=sum(l(root))+sum(r(root));
return root;
}
int ask(int x,int y,int L,int R,int l,int r)
{
if(l<=L&&R<=r)return sum(y)-sum(x);
int mid=(L+R)>>1,ans=0;
if(l<=mid)ans+=ask(l(x),l(y),L,mid,l,r);
if(mid<r)ans+=ask(r(x),r(y),mid+1,R,l,r);
return ans;
}
}T1;
struct sss
{
int q,w,nxt;
}a[1000005];
int head[1000005],cnt,dfn[1000005],f[1000005],son[1000005],s[1000005],top[1000005],d[1000005];
int n,root[1000005],c[1000005],T,low[1000005],P[1000005][25];
void adde(int q,int w)
{
a[++cnt].q=q;
a[cnt].w=w;
a[cnt].nxt=head[q];
head[q]=cnt;
}
void dfs1(int x,int fa)//树剖
{
s[x]=1;
f[x]=fa;
d[x]=d[fa]+1;
P[x][0]=fa;
for(int i=1;i<=20;i++)P[x][i]=P[P[x][i-1]][i-1];//倍增
int id=0;
for(int i=head[x];i;i=a[i].nxt)
{
if(fa^a[i].w)
{
dfs1(a[i].w,x);
s[x]+=a[i].w;
if(s[id]<s[a[i].w])id=a[i].w;
}
}
son[x]=id;
}
void dfs2(int x,int tp)//树剖
{
dfn[x]=++cnt;
low[cnt]=x;
top[x]=tp;
if(!son[x])return;
dfs2(son[x],tp);
for(int i=head[x];i;i=a[i].nxt)if(f[x]!=a[i].w&&a[i].w!=son[x])dfs2(a[i].w,a[i].w);
}
void change(int x,int T){c[x]=1;root[T]=T1.change(root[T-1],1,n,dfn[x]);}
int LCA(int x,int y)//树剖求LCA
{
while(top[x]!=top[y])
{
if(d[top[x]]<d[top[y]])swap(x,y);
x=f[top[x]];
}
if(d[x]<d[y])swap(x,y);
return y;
}
#define pii pair<int,int>
#define mp(x,y) make_pair(x,y)
stack<pii>S;
queue<pii>q;
bool Farmer;
void Push(pii a){if(Farmer)q.push(a);else S.push(a);}
void Pop(){if(Farmer)q.pop();else S.pop();}
pii Front(){if(Farmer)return q.front();return S.top();}
pii Top(){if(Farmer)return q.front();return S.top();}
void Clear(){while(S.size())S.pop();while(q.size())q.pop();}
bool Empty(){if(Farmer)return q.empty();return S.empty();}
//这里是维护被剖出来的区间顺序,通过Farmer来决定是栈还是队列
int Um_nik(int L,int R,int k,int t)//二分
{
int o=L-1,l=L-o,r=R-o,ans;
while(l<=r)
{
int mid=(l+r)>>1;
int u;//计算满足要求的节点数量,记得判断上行或下行
if(Farmer)u=d[low[R]]-d[low[mid+o]]+1-T1.ask(root[t],root[T],1,n,mid+o,R);
else u=d[low[mid+o]]-d[low[L]]+1-T1.ask(root[t],root[T],1,n,L,mid+o);
if(u>=k)
{
if(Farmer)l=mid+1;
else r=mid-1;
ans=mid+o;
}
else Farmer?r=mid-1:l=mid+1;
}
return low[ans];//由于是通过 dfs 序二分,返回要转回原来编号
}
int Ask(int x,int y,int t,int k)
{
if(k>0)Farmer=1;//Farmer 为 1 上行,否则下行
else Farmer=0,k=-k;
Clear();
if(!y)y=1;//特判 y 为 0
else
{
int Y=x;
for(int i=20;i>=0;i--)if(d[P[Y][i]]>d[y])Y=P[Y][i];
y=Y;//查找儿子
}
x=f[x];
if(d[x]<d[y])return -1e9;
int num=0;
while(top[x]!=top[y])//树剖
{
if(d[x]<d[y])swap(x,y);
num+=d[x]-d[top[x]]+1-T1.ask(root[t],root[T],1,n,dfn[top[x]],dfn[x]);
Push(mp(dfn[top[x]],dfn[x]));//将剖下来的区间放入容器中
x=f[top[x]];
}
if(d[x]<d[y])swap(x,y);
num+=d[x]-d[y]+1-T1.ask(root[t],root[T],1,n,dfn[y],dfn[x]);
Push(mp(dfn[y],dfn[x]));
if(num<abs(k))return -num;//如果节点数量不够直接用负数返回节点数
while(!Empty())
{
pii x=Front();
Pop();
int o=d[low[x.second]]-d[low[x.first]]+1-T1.ask(root[t],root[T],1,n,x.first,x.second);
if(o>=k)return Um_nik(x.first,x.second,k,t);//查找第k大
k-=o;
}
return -1e9;
}
int ask(int x,int y,int t,int k)
{
int o=1;
if(d[x]<d[y])swap(x,y),o=-1;
int lca=LCA(x,y);
if(lca==y)return Ask(x,y,t,k*o);//特判y为lca
int p=Ask(o+1?x:y,f[lca],t,k);
if(p>0)return p;//如果p为正数则找到了解
k+=p;
p=Ask(o+1?y:x,lca,t,-k);//用负数表示上下行
return p;
}
int Q;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int q;
scanf("%d",&q);
if(q)adde(i,q),adde(q,i);
}
cnt=0;
root[0]=T1.build(1,n);
dfs1(1,0);
dfs2(1,1);//建树
scanf("%d",&Q);
while(Q--)
{
int op,x,y,k,t;
scanf("%d",&op);
if(op==1)
{
scanf("%d",&x);
change(x,++T);
}
else
{
root[T+1]=root[T];//新建版本
T++;
scanf("%d%d%d%d",&x,&y,&k,&t);
int ans=ask(x,y,t,k);//ans为正为有解
printf("%d\n",ans<=0?-1:ans);
}
}
}