P9999 [Ynoi2000] tmostnrq
题意
给定 \(n\) 个顶点的树,顶点编号为 \(1,\dots,n\),给定长度 \(n_0\) 的序列 \(a_1,\dots,a_{n_0}\),共 \(m\) 次查询,每次查询给定 \(l,r,x\),问树的顶点 \(x\),依次向 \(a_l,\dots,a_r\) 移动一步,到达的顶点。
若 \(x=y\),则从顶点 \(x\) 向 \(y\) 移动一步到达 \(x\),否则到达与 \(x\) 在树上相邻且距离 \(y\) 最近的位置。
$ n,m\le 1e6 $ , 时限 \(12 \mathrm s\) .
题解
发现移动过程不能简单地维护 , 考虑把一次移动看成一个函数 , 这就变成了一个函数复合问题 , 考虑离线用 lxl 讲的插入-标记-回收解决 .
考虑如何处理移动的过程 , 发现向 \(x\) 移动一位相当于把从 \(x\) 到根的路径全沿这条路径下移 , 其他点都上移 , 大概如图 :
处理链的问题 , 就使用树链剖分 , 每次把向下跳的这条链单独处理 , 其余的向上跳打全局标记 .
对于每一条链 , 用以深度为关键字的 FHQ treap 维护 , 每次插入一个点 , 就直接把它插入到对应的链上 .
问题的关键是每一次移动的维护 :
每次处理链时 , 设向这条链上的 \(u\) 移动 , 相当于把平衡树分为 \(<dep_u\) 和 \(>dep_u\) 两部分 , 左边深度 \(+1\) , 右边深度 \(-1\) , 再合并起来 . 这样操作的总复杂度是 \(O(n\log^2 n)\) .
但是发现跳的过程中会出现一个点跳到了不同链上的情况 :
-
第一种 : 设向这条链上的 \(u\) 移动 , 但 \(u\) 不是最初的起点 \(a_i\) , 这时 \(u\) 要通过向 \(a_i\) 方向移动的虚边跳到后一个链上 .
因此如果 \(u\) 处有点 , 就把直接它插入到上一条链中 .
-
第二种 : 如图中蓝边 , 打全局上移标记时 , 原来就在链顶的点会通过虚边上移 .
因此每次操作后需要找到所有出现这种问题的链 , 把它的链顶点删掉插入到它的父亲所在链中 .
处理第二种的方法看起来十分暴力 , 但是稍加分析 , 每个点只有在通过虚边切换时会有额外复杂度 , 如果不考虑下移 , 每个点的切换次数是 $O(\log n) $ 次的 .
而考虑下移 , 每次操作也最多把 $ O(\log n) $ 个点通过虚边下移 , 相当于每次操作使后面多了 \(O(\log n)\) 次上移 . 也就是说 , 视每个点还能向上到根的次数为它的势能 , 总势能是 $O(n\log n+m\log n) $ 的 , 每消除一势能的复杂度是 $O(\log n) $ 的 , 所以这部分复杂度也是 \(O(n\log^2 n)\) , 最终复杂度就是 $O(n\log ^2 n) $ .
为了保证均摊部分的复杂度以及打整体标签的正确性 , 操作的实现都要足够精细 :
-
如果两个点的位置重合 , 需要用并查集把他们并在一起 , 始终保证同一位置最多只有一个点 .
否则 , 就无法保证跨虚边下移时 , 每一条虚边只有一个点下移 , 也就无法保证复杂度 .
-
每次整体打上移标签 , 相当于让深度整体 \(-1\) , 但是我们手动调整的链部分是不能受它影响的 , 所以除了整体标记 \(tottag\) 要对每条链单独维护一个标记 \(tag_i\) . 设平衡树里记录的深度是 \(val\) , 它的深度真实值就应该是 \(val+tottag+tag_i\) .
这样每次打标签的操作就是 \(tottag--\) , 然后所有从 \(x\) 到 \(1\) 经过的链 \(tag_i++\) .
此外 , 为了方便操作 , 可以在对平衡树操作前更新 \(val\gets val+tottag+tag_i , tag_i\gets tottag\) .
-
每次处理上移操作不能遍历所有链判断链顶是否有点 , 正确的做法是维护链中深度最小的点到顶的距离 . 设平衡树上深度最小值为 \(mn\) , 根据我们刚才的定义 , 它到顶的距离是 \(mn+tottag+tag_i-dep_{top}\) , 需要操作的链 , 满足 :
\[mn+tottag+tag_i<dep_{top} \\ mn+tag_i-dep_{top}<-tottag \]可以用
set
维护所有链的 \(mn+tag_i-dep_{top}\) , 每次对平衡树操作时在set
中更新对应的值 . 处理上移操作时直接取出 \(<-tottag\) 的链进行操作即可 . -
注意打标签的时机 , 操作时相当于先手动操作了所有在路径上的链 , 然后对它们 \(tag_i++\) 用来抵消整体 \(tottag\) , 不要一边操作一边动 \(tag\) , 否则可能导致从后面的链下移过来的点位置出错 .
-
查询时可以直接记录对应的平衡树节点编号 ( 注意要取并查集的根 ) , 注意要把这个节点在平衡树上的祖先链都
pushdown
, 还要考虑链上的 \(tag\) , 这样就可以取出它的深度 , 结合所在的重链就可以取出具体位置 .
点击查看代码
#include<bits/stdc++.h>
#define file(x) freopen(#x ".in","r",stdin),freopen(#x ".out","w",stdout)
#define ll long long
using namespace std;
constexpr int N=1e6+5;
constexpr int INF=1e7;
struct node{
int ls,rs,rnd,fa,val,tag;
}a[N];
int at;
inline int add(int dep){
a[++at]={0,0,rand(),0,dep,0};
return at;
}
inline void pushdown(int x){
a[a[x].ls].val+=a[x].tag;
a[a[x].ls].tag+=a[x].tag;
a[a[x].rs].val+=a[x].tag;
a[a[x].rs].tag+=a[x].tag;
a[x].tag=0;
}
inline void pushup(int x){
if(a[x].ls) a[a[x].ls].fa=x;
if(a[x].rs) a[a[x].rs].fa=x;
a[x].fa=0;
}
void split(int u,int k,int &x,int &y){
if(!u) {
x=y=0;return;
}
pushdown(u);
if(a[u].val<=k){
x=u;
split(a[u].rs,k,a[x].rs,y);
pushup(x);
}
else{
y=u;
split(a[u].ls,k,x,a[y].ls);
pushup(y);
}
}
int merge(int x,int y){
if(x==0||y==0) return x+y;
pushdown(x);pushdown(y);
if(a[x].rnd<=a[y].rnd){
a[x].rs=merge(a[x].rs,y);
pushup(x);
return x;
}
else{
a[y].ls=merge(x,a[y].ls);
pushup(y);
return y;
}
return 0;
}
int get(int x,int k){
if(!x) return 0;
pushdown(x);
if(a[x].val>k) return get(a[x].ls,k);
if(a[x].val<k) return get(a[x].rs,k);
return x;
}
int getmin(int x){
if(!x) return INF;
pushdown(x);
if(a[x].ls) return getmin(a[x].ls);
return a[x].val;
}
int fd[N];
int find(int x){
if(x==fd[x]) return x;
fd[x]=find(fd[x]);
return fd[x];
}
int n,tn,m,to[N];
vector<int> e[N];
vector<pair<int,int> > inp[N];
vector<int> oup[N];
int nump[N],res[N];
int fa[N],dep[N],son[N],sz[N],top[N],dfn[N],rnk[N],L[N],R[N],rt[N],tot,pos[N];
int ft[N][24];
void dfs1(int u){
ft[u][0]=fa[u];
for(int i=1;i<=20;i++)
ft[u][i]=ft[ft[u][i-1]][i-1];
sz[u]=1;
for(auto v:e[u]){
if(v==fa[u]) continue;
fa[v]=u;dep[v]=dep[u]+1;
dfs1(v);
sz[u]+=sz[v];
if(sz[v]>sz[son[u]]) son[u]=v;
}
}
int tottag,tag[N],mn[N];
set<pair<int,int> > st;
void dfs2(int u,int t){
dfn[u]=++tot;rnk[tot]=u;
top[u]=t;
if(u==t){
L[t]=u;
mn[t]=INF;tag[t]=0;
st.insert({INF-dep[t],t});
}
R[t]=u;
if(son[u])
dfs2(son[u],t);
for(auto v:e[u])
if(v!=fa[u]&&v!=son[u])
dfs2(v,v);
}
inline void pushtag(int t){
int tg=tottag+tag[t];
a[rt[t]].val+=tg,a[rt[t]].tag+=tg;
mn[t]+=tg;
tag[t]=-tottag;
}
inline void insert(int x,int t){
if(!x) return;
int d=a[x].val;
pushtag(t);
int link=get(rt[t],d);
if(link) fd[x]=link;
else{
st.erase({mn[t]+tag[t]-dep[t],t});
int u,v;split(rt[t],d,u,v);
rt[t]=merge(u,merge(x,v));
pos[rt[t]]=t;
mn[t]=getmin(rt[t]);
st.insert({mn[t]+tag[t]-dep[t],t});
}
}
inline void solve(int x){
int u,v,w,y,z,tmp,t;
pushtag(top[x]);
split(rt[top[x]],dep[x]-2,u,tmp);
split(tmp,dep[x]+1,v,z);tmp=v;
split(tmp,dep[x]-1,v,y);tmp=y;
split(tmp,dep[x],w,y);
a[v].val++;a[v].tag++;a[y].val--;a[y].tag--;
if(v&&w) fd[v]=w,v=0;
if(y&&w) fd[y]=w,y=0;
if(v&&y) fd[v]=y,v=0;
a[u].val++;a[u].tag++;a[z].val--;a[z].tag--;
rt[top[x]]=merge(u,merge(v+w+y,z));
pos[rt[top[x]]]=top[x];
int last=x;
while(top[x]!=1){
last=x;
x=fa[top[x]];
pushtag(top[x]);
split(rt[top[x]],dep[x]-2,u,tmp);
split(tmp,dep[x]+1,v,z);tmp=v;
split(tmp,dep[x]-1,v,y);tmp=y;
split(tmp,dep[x],w,y);
t=top[last];
st.erase({mn[t]+tag[t]-dep[t],t});
if(w){
a[w].val++,a[w].tag++;
int d=a[w].val;
int link=get(rt[t],d);
if(link) fd[w]=link;
else rt[t]=merge(w,rt[t]);
pos[rt[t]]=t;
}
tag[t]++;
mn[t]=getmin(rt[t]);
st.insert({mn[t]+tag[t]-dep[t],t});
a[v].val++;a[v].tag++;a[y].val--;a[y].tag--;
if(v&&y) fd[v]=y,v=0;
a[u].val++;a[u].tag++;a[z].val--;a[z].tag--;
rt[top[x]]=merge(u,merge(v+y,z));
pos[rt[top[x]]]=top[x];
}
t=top[x];
st.erase({mn[t]+tag[t]-dep[t],t});
tag[t]++;
mn[t]=getmin(rt[t]);
st.insert({mn[t]+tag[t]-dep[t],t});
tottag--;
vector<int> d;d.clear();
for(auto x:st){
if(x.first>=-tottag) break;
d.push_back(x.second);
}
for(auto t:d){
pushtag(t);
st.erase({mn[t]+tag[t]-dep[t],t});
split(rt[t],dep[t]-1,u,v);
rt[t]=v;pos[v]=t;
mn[t]=getmin(rt[t]);
st.insert({mn[t]+tag[t]-dep[t],t});
insert(u,top[fa[t]]);
}
}
inline int query_pos(int u,int d){
for(int i=20;i>=0;i--)
if(dep[ft[u][i]]>=d) u=ft[u][i];
return u;
}
int q[N],tp;
inline int query(int x){
tp=0;q[++tp]=x;
while(a[x].fa) q[++tp]=a[x].fa,x=a[x].fa;
int st=R[pos[q[tp]]];
pushtag(pos[q[tp]]);
while(tp) pushdown(q[tp--]);
return query_pos(st,a[q[1]].val);
}
namespace debug{
int s;
int tres[N],fa[N];
void dfs(int u){
for(auto v:e[u]){
if(v==fa[u]) continue;
fa[v]=u; dfs(v);
}
}
int solve(int l,int r,int x){
for(int i=l;i<=r;i++){
fa[to[i]]=to[i];
dfs(to[i]);
x=fa[x];
}
return x;
}
void init(){
cin>>s;srand(s);
n=15;tn=10;m=10;
cout<<n<<' '<<tn<<' '<<m<<'\n';
for(int i=2;i<=n;i++){
int tmpf=rand()%(i-1)+1;
cout<<tmpf<<' ';
e[tmpf].push_back(i);
e[i].push_back(tmpf);
}
cout<<'\n';
for(int i=1;i<=tn;i++){
to[i]=rand()%n+1;
cout<<to[i]<<' ';
}
cout<<'\n';
for(int i=1;i<=m;i++) {
int l=rand()%tn+1,r=rand()%tn+1,x=rand()%n+1;
if(l>r) swap(l,r);
cout<<l<<' '<<r<<' '<<x<<'\n';
inp[l].push_back({i,x});
oup[r].push_back(i);
tres[i]=solve(l,r,x);
}
}
void check(){
for(int i=1;i<=m;i++){
if(res[i]!=tres[i]){
cout<<"WA! pos:"<<i<<" answer="<<tres[i];
return;
}
}
cout<<"AC!";
}
}
void input(){
cin>>n>>tn>>m;
for(int i=2;i<=n;i++) {
int u,v;cin>>u;v=i;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=tn;i++) cin>>to[i];
for(int i=1;i<=m;i++){
int l,r,x;cin>>l>>r>>x;
inp[l].push_back({i,x});
oup[r].push_back(i);
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
//debug::init();
input();
dep[1]=1;dfs1(1);dfs2(1,1);
for(int i=1;i<=m;i++) fd[i]=i;
for(int i=1;i<=tn;i++){
for(auto [id,u]:inp[i]){
int now=add(dep[u]);
insert(now,top[u]);
nump[id]=now;
}
solve(to[i]);
for(auto id:oup[i]){
res[id]=query(find(nump[id]));
}
}
for(int i=1;i<=m;i++) cout<<res[i]<<'\n';
//debug::check();
}
总结
结合 lxl 讲的这一类问题的套路 , 这道题的思路其实还是很清晰的 . 甚至看起来细节都不是特别繁琐 .
然而实现起来还是非常难写 , 因为维护的是深度 , 还有全局打 tag , 合并相同点的问题 , 导致所有操作都没有看起来那么直接 .
因为以下问题调了 14h+ :
-
打标签时机出错 .
-
并查集预处理要 \(m\) 个而非 \(n\) 个.
-
pushup(y)
写成pushup(x)
. -
合并点少考虑情况 .
-
查询时没有考虑全局 tag
其中最后一个问题卡了一个上午加一个下午 . 手搓数据全都没看出问题 , 最后是靠对拍拍出了问题 . 所以在真的找不出问题时还是对拍比较有效 .
不管怎么说终于调出来这道题了 .
标签:rt,P9999,mn,val,tmostnrq,int,Ynoi2000,dep,tag From: https://www.cnblogs.com/youlv/p/18667075