Description
一棵 n 个点的树,点权最初为 1 ∼ n 的排列。
定义一个删点过程:每次找到权值最小的叶子,删去它以及连接的边,重复这个过程直到剩下一个点,然后删去最后的点。
处理 q 个询问:
- 将一个点 x 的权值赋为当前最大权值 +1。
- 查询在删点过程中,x 会是第几个被删除的。
n, q ≤ 2×1052 \times 10^52×105
Solution
- 观察到删除序列的一些性质:
- 权值最大的点最后一个被删。
- 权值最大的点为 x,次大的为 y,那么序列最后一段就是 y 到 x 的简单路径。
- 不难分析,一次赋值操作,不会影响非 y → x 路径上的点的变叶子过程,所以造成的影响就是将 y → x 路径提到序列最后,其他点的顺序不变。(设给y赋值,x为原权值最大的点)
- 所以,不妨先把初始序列模拟出来,然后维护它。
- 转化一下模型:修改权值变为将 y → x 的路径染上一种新的颜色。
- 那么询问 z 就变成了;多少点的颜色出现早于 z 颜色,以及与 z 同色的点优先于 z 的个数。
- 维护颜色/同色的优先级可以用树链剖分 + 线段树 或 树链剖分 + ODT,每种颜色的点数可以直接树状数组。
- 具体讲ODT做法:先树剖,对于每一条重链,维护一个set,set里维护这条重链上的颜色区间。那么一次区间覆盖只要把旧的区间删去,并加入新的区间就行了。
Code
#include<iostream>
#include<cstdio>
#include<queue>
#include<set>
using namespace std;
const int N=200035;
const int M=400035;
struct Edge{int v,nxt;
}edge[M];
int n,q,cnt,head[N],tim,root[N*2];
int deg[N],fa[N],dep[N],siz[N],son[N],top[N],dfn[N],ind;
char opt[20];
struct node{//ODTint l,r,val;node(int a=0,int b=0,int c=0):l(a),r(b),val(c) {}bool operator < (node b) const {return l<b.l;}
};
set<node> s[N];
typedef set<node>::iterator itr;
priority_queue<int,vector<int>,greater<int> > que;
namespace BIT{int c[N*2];void add(int i,int x){for(i+=5;i<=n+q+5;i+=i&-i) c[i]+=x;}int sum(int i){int res=0;for(i+=5;i;i-=i&-i) res+=c[i];return res;}
};
void add(int u,int v){edge[++cnt].v=v;edge[cnt].nxt=head[u];head[u]=cnt;
}
void dfs1(int u,int f){fa[u]=f;dep[u]=dep[f]+1;top[u]=son[u]=-1;siz[u]=1;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].v;if(v==f) continue;dfs1(v,u);siz[u]+=siz[v];if(son[u]==-1||siz[v]>siz[son[u]]) son[u]=v;}
}
void dfs2(int u,int tp){if(u==tp){s[u].insert(node(n+1,n+1,0));}dfn[u]=++ind;top[u]=tp;if(son[u]==-1) return;dfs2(son[u],tp);for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].v;if(v!=fa[u]&&v!=son[u]) dfs2(v,v);}
}
itr split(int i,int pos){itr loc=s[i].lower_bound(node(pos));if(loc!=s[i].end()&&loc->l==pos) return loc;int l=(--loc)->l,r=loc->r,val=loc->val;s[i].erase(loc);s[i].insert(node(l,pos-1,val));return s[i].insert(node(pos,r,val)).first;
}
void insert(int i,int l,int r,int c){itr rpos=split(i,r+1),lpos=split(i,l);for (itr it=lpos;it!=rpos;++it)BIT::add(it->val,-(it->r-it->l+1));//遍历清除历史颜色s[i].erase(lpos,rpos);BIT::add(c,r-l+1);s[i].insert(node(l,r,c));
}
int LCA(int u,int v){while(top[u]!=top[v]){if(dep[top[u]]<dep[top[v]]) v=fa[top[v]];else u=fa[top[u]];}if(dep[u]<dep[v]) return u;return v;
}
int dist(int u,int v){int lca=LCA(u,v);return dep[u]+dep[v]-(dep[lca]<<1);
}
void ChainModify(int u,int v){while(top[u]!=top[v]){if(dep[top[u]]<dep[top[v]]) swap(u,v);insert(top[u],dfn[top[u]],dfn[u],tim);u=fa[top[u]];}if(dep[u]<dep[v]) swap(u,v);insert(top[v],dfn[v],dfn[u],tim);
}
int query(int u){int c=split(top[u],dfn[u])->val;return BIT::sum(c-1)+dist(u,root[c-1])+1;//root[c-1]是指上一个历史版本节点编号最大的一个元素
}
int main(){scanf("%d%d",&n,&q);for(int i=1;i<n;i++){int x,y;scanf("%d%d",&x,&y);add(x,y);add(y,x);deg[x]++;deg[y]++;}dfs1(1,0);dfs2(1,1);//先搞出初始序列: for(int i=1;i<=n;i++) if(deg[i]==1) que.push(i);for(int i=1;i<=n;i++){int u=que.top();que.pop();root[i-1]=root[i]=u;//写成root[i-1]=root[i]=u是因为对于初始序列,查询时候要保持dist(u,root[c-1])=0;但是对于之后修改的询问root[n]应该为nBIT::add(i,1);deg[u]=0;s[top[u]].insert(node(dfn[u],dfn[u],++tim));//node不加括号啊啊啊,为这个调了我一天 for(int i=head[u];i;i=edge[i].nxt)if((--deg[edge[i].v])==1) que.push(edge[i].v); }//不断修改、查询序列: for(int i=1;i<=q;i++){//用while(q--)就错?why? int u,v;scanf("%s",opt);if(opt[0]=='u'){scanf("%d",&u);root[++tim]=u;ChainModify(u,root[tim-1]);}else if(opt[0]=='w'){scanf("%d",&u);printf("%d\n",query(u));}else{scanf("%d%d",&u,&v);printf("%d\n",(query(u)<query(v))?u:v);}}return 0;
}
参考文章:
https://www.cnblogs.com/bztMinamoto/p/10537308.html
https://www.cnblogs.com/antiquality/p/10660122.html